1. Introduction

In today’s competitive market, understanding customer behavior is crucial for driving profitability and long-term success. By analyzing transaction data, businesses can gain insights into key factors such as customer demographics, marketing channel performance, and profitability trends. These insights enable businesses to make data-driven decisions to enhance customer retention, optimize marketing strategies, and increase overall revenue.

This project leverages a comprehensive dataset to explore these dynamics, focusing on actionable outcomes and recommendations.


Dataset Column Information

Below is the description of each column in the dataset:

SN Column Name Column Description
1 Customer_ID Unique identifier for each customer
2 Gender Gender of the customer (Male/Female)
3 Date_Of_Birth Birth date of the customer in MM/DD/YYYY format
4 Location Location (city) of the customer
5 Contact_Allowed Whether the customer has allowed to be contacted (Y/N)
6 Registration_Date Date and time when the customer registered
7 Marketing_Channel_Type The marketing channel through which the customer was acquired
8 First_Order_Profit Profit from the customer’s first order
9 Subsequent_Order_Profit Profit from all subsequent orders of the customer
10 Subsequent_Orders_Count Number of subsequent orders placed by the customer
11 Total_value_of_all_promotions Total value of all promotions the customer received
12 Age Age of the customer (calculated from Date_Of_Birth)
13 Total_Profit Total profit generated from the customer (First + Subsequent)

Goal

The primary goal of this project is to analyze customer behavior and profitability in relation to various marketing channels. By leveraging the dataset, which includes essential customer and transaction details, we aim to uncover factors that drive Customer Lifetime Value (CLV) and measure the effectiveness of different marketing strategies.

Through this analysis, the business will gain actionable insights to: - Optimize marketing efforts. - Improve customer retention. - Increase overall profits.


Deliverables

1. Data Cleaning and Transformation

  • Clean and preprocess the dataset by:
    • Handling missing values.
    • Detecting and addressing outliers.
    • Converting columns to appropriate formats for analysis.

2. Exploratory Data Analysis (EDA)

  • Perform statistical analysis and visualizations to:
    • Identify trends, relationships, and patterns.
    • Highlight anomalies or unusual behaviors in the data.

3. Outlier Detection

  • Purpose: Detect unusual patterns in customer transactions, such as:
    • Exceptionally high profits
    • Promotions or discounts
    • Unusual order behavior
  • Goal: Understand exceptional cases, refine marketing strategies, and manage both high-value and low-value customers effectively.

Summary

This analysis will empower the business to: - Understand its customer base more deeply. - Tailor marketing strategies to maximize engagement and profitability. - Refine campaigns to improve customer satisfaction and long-term growth.

By focusing on these objectives, the business can turn data-driven insights into impactful actions.



2. Data Exploration and Preprocessing

Loading and Previewing the Data

In this section, we will load the dataset from a CSV file and preview the first few rows.

# Load the CSV file
df <- read.csv("../data/marketing_data.csv")

Displaying the First Few Rows of the Dataset

# Display the first few rows
head(df)

Dataset Overview


Number of Rows and Columns

# Total number of rows
num_rows <- dim(df)[1]

# Total number of columns
num_columns <- dim(df)[2]

# Print the results
cat("Number of rows:", num_rows, "\n")
Number of rows: 30591 
cat("Number of columns:", num_columns, "\n")
Number of columns: 13 

Checking Column Names

# Get column names
as.data.frame(colnames(df))

Checking for NA values and Handling Missing Data

as.data.frame(colSums(is.na(df)))

Identifying and Removing Duplicates

# Find duplicate rows
df[duplicated(df), ]

Displaying the Data Structure

# Check the structure of the dataset
str(df)
'data.frame':   30591 obs. of  13 variables:
 $ Customer_ID                  : int  1 2 3 4 5 6 7 8 9 10 ...
 $ Gender                       : chr  "Male" "Male" "Male" "Male" ...
 $ Date_Of_Birth                : chr  "6/29/1973" "7/25/1984" "5/1/1991" "11/30/1986" ...
 $ Location                     : chr  "Malahide" "Dublin" "Dublin" "Dublin" ...
 $ Contact_Allowed              : chr  "Y" "Y" "Y" "N" ...
 $ Registration_Date            : chr  "1/1/2013 0:23" "1/1/2013 0:30" "1/1/2013 1:09" "1/1/2013 1:19" ...
 $ Marketing_Channel_Type       : chr  "Organic Search" "Affiliates" "Paid Social" "Affiliates" ...
 $ First_Order_Profit           : num  8.97 7.92 14.06 14.08 22.68 ...
 $ Subsequent_Order_Profit      : num  5.31 70.16 50.8 0 68.22 ...
 $ Subsequent_Orders_Count      : int  1 7 7 0 9 3 0 8 5 1 ...
 $ Total_value_of_all_promotions: num  6.6 6.64 0 0 14.77 ...
 $ Age                          : int  39 28 21 26 21 26 40 36 47 23 ...
 $ Total_Profit                 : num  14.3 78.1 64.9 14.1 90.9 ...

Generating Summary of Data

# Get a statistical summary of numerical columns
summary(df)
  Customer_ID       Gender          Date_Of_Birth        Location         Contact_Allowed   
 Min.   :    1   Length:30591       Length:30591       Length:30591       Length:30591      
 1st Qu.: 7862   Class :character   Class :character   Class :character   Class :character  
 Median :15722   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
 Mean   :15721                                                                              
 3rd Qu.:23582                                                                              
 Max.   :31441                                                                              
 Registration_Date  Marketing_Channel_Type First_Order_Profit Subsequent_Order_Profit
 Length:30591       Length:30591           Min.   : 0.882     Min.   :  0.00         
 Class :character   Class :character       1st Qu.: 5.127     1st Qu.:  0.00         
 Mode  :character   Mode  :character       Median : 9.576     Median : 21.38         
                                           Mean   :10.682     Mean   : 37.44         
                                           3rd Qu.:14.828     3rd Qu.: 57.10         
                                           Max.   :57.974     Max.   :412.66         
 Subsequent_Orders_Count Total_value_of_all_promotions      Age        Total_Profit    
 Min.   : 0.000          Min.   : -0.04992             Min.   :19.0   Min.   :  0.882  
 1st Qu.: 0.000          1st Qu.:  0.00000             1st Qu.:26.0   1st Qu.: 12.787  
 Median : 3.000          Median :  1.88520             Median :31.0   Median : 32.046  
 Mean   : 4.054          Mean   :  6.47948             Mean   :33.9   Mean   : 48.121  
 3rd Qu.: 7.000          3rd Qu.:  8.11833             3rd Qu.:40.0   3rd Qu.: 68.625  
 Max.   :32.000          Max.   :145.26473             Max.   :96.0   Max.   :415.832  

3. Explatory Data Analysis


Histogram Analysis

This section presents the histograms for the following six key variables from the dataset:

  • First Order Profit
  • Subsequent Order Profit
  • Subsequent Orders Count
  • Total Value of All Promotions
  • Age
  • Total Profit

These histograms provide insights into the distributions of the data, helping us identify skewness, the presence of outliers, and potential data transformations.

# Select specific columns
selected_cols <- c(
  "First_Order_Profit", "Subsequent_Order_Profit", "Subsequent_Orders_Count",
  "Total_value_of_all_promotions", "Age", "Total_Profit"
)

# Automatically extract the required number of colors
colors <- get_rose_pine_colors(length(selected_cols)) 

# Prepare the data list
data_list <- lapply(selected_cols, function(col) df[[col]])
names(data_list) <- selected_cols

# Generate Histograms Dynamically
histograms <- lapply(seq_along(data_list), function(i) {
  formatted_title <- tools::toTitleCase(gsub("_", " ", names(data_list)[i]))  # Format column name
  
  ggplot(data.frame(value = data_list[[i]]), aes(x = value)) +
    geom_histogram_default(data_length = length(data_list[[i]]), fill_color = colors[i]) +  # Dynamic bins & color
    labs(
      title = paste("", formatted_title),  # Use formatted title
      x = "",
      y = "Frequency"
    ) + 
    theme_rose_pine()
})

# Combine All Histograms into a Grid Layout
do.call(grid.arrange, c(histograms, ncol = 3))

Observations and Recommendations

1. First Order Profit

  • Observation:
    • Highly right-skewed distribution with most values near zero.
    • A long tail suggests high-profit outliers.
  • Recommendation:
    • Apply a logarithmic transformation to reduce skewness for analysis.
    • Investigate outliers to determine their validity or consider capping them.

2. Subsequent Order Profit

  • Observation:
    • Right-skewed with most values near zero and a few very high profits.
    • The distribution is similar to the First Order Profit.
  • Recommendation:
    • Use similar treatment as First Order Profit (e.g., logarithmic transformation or outlier capping).
    • Explore customer segments that generate high subsequent profits.

3. Subsequent Orders Count

  • Observation:
    • Majority of customers placed very few subsequent orders (≤ 5).
    • A sharp decline in customer count as the number of orders increases.
  • Recommendation:
    • Group higher order counts into bins for better visual interpretability.
    • Investigate factors influencing customers with higher order counts (e.g., age, promotions).

4. Total Value of All Promotions

  • Observation:
    • Highly skewed with most customers receiving promotions of low value.
    • Few customers received significantly high-value promotions, creating a long tail.
  • Recommendation:
    • Investigate whether high promotion values correlate with high profit or order counts.
    • Consider grouping promotion values into bins for better interpretability.

5. Age

  • Observation:
    • Approximately bell-shaped, with most customers falling between 20–50 years of age.
    • Fewer customers are younger than 20 or older than 50.
  • Recommendation:
    • No immediate action required; the distribution is balanced and can be used as-is for modeling.

6. Total Profit

  • Observation:
    • Similar to profit variables, the distribution is right-skewed with most values concentrated near zero.
    • High-profit outliers are present.
  • Recommendation:
    • Investigate high-profit customers and their characteristics.
    • Consider transformations or outlier handling for modeling purposes.

This analysis helps identify potential preprocessing steps and guides exploratory analysis to better understand customer behavior.


Density Plot Analysis

This section provides observations and recommendations based on the density plots of the following variables:

  • First Order Profit
  • Subsequent Order Profit
  • Subsequent Orders Count
  • Total Value of All Promotions
  • Age
  • Total Profit
# Generate KDE plots dynamically
kde <- lapply(seq_along(data_list), function(i) {
  formatted_title <- tools::toTitleCase(gsub("_", " ", names(data_list)[i]))
  
  ggplot(data.frame(value = data_list[[i]]), aes(x = value)) +
    geom_density_default(fill_color = colors[i]) +  # Dynamic color assignment
    labs(
      title = paste("", formatted_title),
      x = "",
      y = "Density"
    ) +
    theme_rose_pine()
})

# Combine all plots into a grid layout
do.call(grid.arrange, c(kde, ncol = 3))

Observations and Recommendations

1. First Order Profit

  • Observation:
    • Right-skewed distribution with a sharp peak at low profit values.
    • Long tail due to high-profit customers.
  • Recommendation:
    • Apply a logarithmic transformation for skewness correction.
    • Examine outliers for validity or apply capping to reduce influence.

2. Subsequent Order Profit

  • Observation:
    • Sharp peak at low values, with a similar right-skewed trend as First Order Profit.
    • Long tail extending towards very high values.
  • Recommendation:
    • Consider handling outliers through capping or Winsorization.
    • Investigate customer segments contributing to high subsequent profits.

3. Subsequent Orders Count

  • Observation:
    • Clear peak at 1–2 orders, indicating most customers made few repeat purchases.
    • Distribution flattens significantly for higher order counts.
  • Recommendation:
    • Group higher counts into bins for clearer insights.
    • Explore factors (e.g., demographics, promotions) influencing customers with many orders.

4. Total Value of All Promotions

  • Observation:
    • Highly skewed, with most values concentrated near zero.
    • Long tail suggests a few customers received significantly high promotion values.
  • Recommendation:
    • Assess whether high promotion values translate to higher profits or order counts.
    • Consider segmenting customers based on promotion levels for further analysis.

5. Age

  • Observation:
    • Distribution is approximately bell-shaped, centered around 30–40 years.
    • Skewness is minimal, with balanced data for most age ranges.
  • Recommendation:
    • No transformation needed. The variable can be used as-is in analysis.

6. Total Profit

  • Observation:
    • Similar right-skewed trend as other profit variables, with a peak at low values.
    • Long tail shows the presence of a few high-profit customers.
  • Recommendation:
    • Consider transformations to address skewness and reduce outlier effects.
    • Investigate high-profit customers to understand their characteristics.

This density plot analysis highlights key trends and provides actionable recommendations for preprocessing and further exploration.


Gender Distribution Analysis

This section visualizes the distribution of gender across the dataset using a bar plot and a pie chart.

gender_counts <- as.data.frame(table(df$Gender))
names(gender_counts) <- c("Gender", "Count")

# Create Count Plot using geom_count_default
count_plot <- ggplot(gender_counts, aes(x = Gender, y = Count, fill = Gender)) +
  geom_bar(stat = "identity") +
  theme_rose_pine() +  # Apply the custom theme
  scale_fill_rose_pine() +  # Apply the fill color scale
  labs(title = "Gender Distribution Plot", x = "Gender", y = "Count") +  # Title and axis labels
  theme(
    plot.title = element_text(size = 18, face = "bold", color = "white"),  # Title font size and color
    axis.title.x = element_text(size = 14, color = "white"),  # X-axis label font size and color
    axis.title.y = element_text(size = 14, color = "white"),  # Y-axis label font size and color
    axis.text = element_text(size = 12, color = "white"),  # Axis tick labels font size and color
    legend.position = "right",
    legend.title = element_text(size = 13, color = "white"),  # Legend title font size and color
    legend.text = element_text(size = 11, color = "white"),  # Legend text font size and color
    legend.key.size = unit(1, "cm")  # Adjust the size of legend keys (the color boxes)
  )

# Generate the Pie Chart
pie_chart <- geom_pie_default(
  data = gender_counts,
  category_col = "Gender",
  value_col = "Count"
) + 
  theme(
    legend.position = "none",
)

# Combine the plots using grid.arrange
grid.arrange(count_plot, pie_chart, ncol = 2)

Observations

1. Bar Plot

  • Observation:
    • The majority of customers are Male, making up the largest group.
    • A smaller proportion of customers are Female.
    • A notable percentage of customers fall into the Not Defined category, which might indicate missing or unspecified data.

2. Pie Chart

  • Observation:
    • Male customers constitute approximately 63.5% of the dataset.
    • Female customers account for 23.5%.
    • The Not Defined category represents 13%.

Recommendations

  • Investigate the Not Defined category to determine whether this data can be clarified or excluded.
  • Use this distribution to segment analysis or marketing strategies based on gender.

Marketing Channel Type Distribution

This section visualizes the distribution of marketing channels, providing insights into which channels are most effective in acquiring customers.

marketing_channel_type_count <- as.data.frame(table(df$Marketing_Channel_Type))
names(marketing_channel_type_count) <- c("Marketing_Channel_Type", "Count")

# Create Count Plot using geom_count_default
count_plot <- ggplot(marketing_channel_type_count, aes(x = Marketing_Channel_Type, y = Count, fill = Marketing_Channel_Type)) +
  geom_bar(stat = "identity") +
  theme_rose_pine() +  # Apply the custom theme
  scale_fill_rose_pine() +  # Apply the fill color scale
  labs(title = "Marketing Channel Type Distribution", x = "", y = "Count") +  # Title and axis labels
  theme(
    plot.title = element_text(size = 18, face = "bold", color = "white"),  # Title font size and color
    #axis.title.x = element_text(size = 14, color = "white"),  # X-axis label font size and color
    axis.text.x = element_blank(),  # Hide x-axis text
    axis.title.y = element_text(size = 14, color = "white"),  # Y-axis label font size and color
    axis.text = element_text(size = 12, color = "white", angle = 90),  # Axis tick labels font size and color
    legend.position = "right",
    legend.title = element_text(size = 13, color = "white"),  # Legend title font size and color
    legend.text = element_text(size = 11, color = "white"),  # Legend text font size and color
    legend.key.size = unit(1, "cm")  # Adjust the size of legend keys (the color boxes)
  )

# Generate the Pie Chart
pie_chart <- geom_pie_default(
  data = marketing_channel_type_count,
  category_col = "Marketing_Channel_Type",
  value_col = "Count"
) + 
  theme(
    legend.position = "none",
)

# Combine the plots using grid.arrange
grid.arrange(count_plot, pie_chart, ncol = 2)

Observations

1. Bar Plot

  • Observation:
    • The Direct channel contributes the largest share, with approximately 35.8% of customers.
    • Organic Search and Paid Search are the second and third most common channels, at 26.7% and 20.8%, respectively.
    • Affiliates represent 12.3%, and Paid Social contributes the smallest share at 4.5%.

2. Pie Chart

  • Observation:
    • The proportional view confirms that Direct, Organic Search, and Paid Search are the dominant channels.
    • Paid Social, despite being a minor channel, might target niche customer segments.

Recommendations

  • Focus marketing efforts on Direct and Organic Search channels, as they have a significant impact.
  • Evaluate the performance of Paid Social to determine whether it is cost-effective or requires adjustments.
  • Explore opportunities to improve customer acquisition through Affiliates by refining the strategy.

Contact Allowed Distribution

This section examines the distribution of customer consent for being contacted, represented by two visualizations: a bar plot and a pie chart.

contact_allowed_count <- as.data.frame(table(df$Contact_Allowed))
names(contact_allowed_count) <- c("Contact_Allowed", "Count")

# Create Count Plot using geom_count_default
count_plot <- ggplot(contact_allowed_count, aes(x = Contact_Allowed, y = Count, fill = Contact_Allowed)) +
  geom_bar(stat = "identity") +
  theme_rose_pine() +  # Apply the custom theme
  scale_fill_rose_pine() +  # Apply the fill color scale
  labs(title = "Contact Allowed Distribution", x = "", y = "Count") +  # Title and axis labels
  theme(
    plot.title = element_text(size = 18, face = "bold", color = "white"),  # Title font size and color
    #axis.title.x = element_text(size = 14, color = "white"),  # X-axis label font size and color
    axis.text.x = element_blank(),  # Hide x-axis text
    axis.title.y = element_text(size = 14, color = "white"),  # Y-axis label font size and color
    axis.text = element_text(size = 12, color = "white", angle = 90),  # Axis tick labels font size and color
    legend.position = "right",
    legend.title = element_text(size = 13, color = "white"),  # Legend title font size and color
    legend.text = element_text(size = 11, color = "white"),  # Legend text font size and color
    legend.key.size = unit(1, "cm")  # Adjust the size of legend keys (the color boxes)
  )

# Generate the Pie Chart
pie_chart <- geom_pie_default(
  data = contact_allowed_count,
  category_col = "Contact_Allowed",
  value_col = "Count"
) + 
  theme(
    legend.position = "none",
)

# Combine the plots using grid.arrange
grid.arrange(count_plot, pie_chart, ncol = 2)

Observations

1. Bar Plot

  • Observation:
    • Approximately 57.7% of customers have allowed contact.
    • 42.3% of customers have opted out of being contacted.

2. Pie Chart

  • Observation:
    • The pie chart confirms that the majority of customers allow contact, though a significant portion prefers not to be contacted.

Recommendations

  • Focus outreach and marketing strategies on the 57.7% of customers who allow contact.
  • Consider alternative, non-intrusive marketing strategies for the 42.3% of customers who do not allow contact, such as personalized emails or targeted ads.

Top 20 Location Count Analysis

This section explores the distribution of customer counts across the top 20 locations in the dataset.

# Calculate Gender Counts (Location Counts)
location_counts <- data.frame(table(df$Location))
names(location_counts) <- c("Location", "Count")

# Filter for the top 20 locations by count
top_20_locations <- location_counts %>%
  arrange(desc(Count)) %>%
  head(20)

# Count Plot for Top 20 Locations
count_plot <- ggplot(top_20_locations, aes(x = Location, y = Count, fill = Location)) +
  geom_bar(stat = "identity") +
  theme_rose_pine() +  # Apply the custom theme
  scale_fill_rose_pine() +  # Apply the fill color scale
  labs(title = "Top 20 Location Count Plot", x = "Location", y = "Count") +  # Title and axis labels
  theme(legend.position = "none", 
        axis.text.x = element_text(angle = 90, hjust = 1))  # Rotate x-axis labels for better readability

# Show the count plot
count_plot

Observations

Bar Plot

  • Observation:
    • Dublin dominates the dataset, contributing the highest count of customers, significantly surpassing all other locations.
    • Other locations such as Cork, Galway, and Limerick contribute a smaller but noticeable count.
    • A long tail exists for other locations, indicating a relatively small representation from these areas.

Recommendations

  • Given Dublin’s significant contribution, marketing and business strategies should focus heavily on this region.
  • For smaller locations, consider regional campaigns or promotions to increase customer engagement.
  • Evaluate if the concentration in Dublin skews the dataset or limits insights into other regions.

Box Plot Analysis

This section examines the distribution and presence of outliers across six key variables using box plots. The visualizations provide a clear understanding of data variability and potential anomalies.

# Generate Boxplots Dynamically
box_plot_list <- lapply(seq_along(data_list), function(i) {
  formatted_title <- tools::toTitleCase(gsub("_", " ", names(data_list)[i]))
  
  ggplot(df, aes(x = factor(1), y = data_list[[i]])) +
    geom_boxplot_default(data = df, x_col = "factor(1)", y_col = names(data_list)[i], fill_color = colors[i]) +  # Dynamic colors
    labs(
      title = paste("", formatted_title),
      x = "Category", 
      y = "Value"
    ) +
    theme_rose_pine() +  # Apply Rose Pine theme
    theme(
      axis.text.x = element_blank(),  # Remove x-axis text
      axis.ticks.x = element_blank(),  # Remove x-axis ticks
      plot.title = element_text(size = 10, face = "bold")  # Title formatting
    )
})

# Combine All Boxplots into a Grid Layout using grid.arrange
grid.arrange(grobs = box_plot_list, ncol = length(selected_cols))  # Arrange plots horizontally

Observations

1. First Order Profit

  • Observation:
    • The majority of data points are clustered below 20 units.
    • A few extreme outliers extend up to 60 units, suggesting a skewed distribution.

2. Subsequent Order Profit

  • Observation:
    • Most values are below 100 units, with significant outliers reaching beyond 400 units.
    • Highlights variability in profit generation from subsequent orders.

3. Subsequent Orders Count

  • Observation:
    • Data is tightly clustered below 10 orders, with a few outliers exceeding 30 orders.

4. Total Value of All Promotions

  • Observation:
    • The majority of data points fall below 20 units.
    • Numerous extreme outliers exceed 100 units, indicating promotional anomalies.

5. Age

  • Observation:
    • Most customers are between 20 and 50 years old.
    • Few outliers extend beyond 80 years, which could indicate data inconsistencies or older customers.

6. Total Profit

  • Observation:
    • The bulk of data is below 100 units.
    • Outliers extend beyond 400 units, pointing to high-value customers or data irregularities.

Recommendations

  • Outlier Treatment: Consider winsorizing or transforming data to minimize the influence of extreme outliers.
  • Data Quality Check: Verify the accuracy of outlier data points, particularly for variables like Age and Total Value of All Promotions.
  • Further Analysis: Investigate the relationship between high outliers in Total Profit and promotional activity.

Age Distribution by Gender

This section explores the distribution of customers’ ages, segmented by gender. The visualization provides insights into age demographics and highlights gender-based trends.

ggplot(df, aes(x = Age, fill = Gender)) + # Group by Gender
  geom_histogram(binwidth = 0.5, position = "dodge") +
  labs(
    title = "Age Distribution by Gender",
    x = "Age",
    y = "Frequency"
  ) +
  theme_rose_pine() +
  scale_fill_rose_pine()

Observations

  1. Overall Age Distribution:
    • The majority of customers fall between 20 and 50 years.
    • A sharp decline in frequency is observed beyond 50 years, with sparse data for customers above 80 years.
  2. Gender Segmentation:
    • Males form the largest segment across all age groups.
    • Females represent a smaller but consistent portion across most age groups.
    • Not Defined gender has minimal representation, primarily in the younger age brackets.
  3. Peaks in Age:
    • Noticeable peaks around 25–30 years, indicating a concentrated customer base in this age range.

Recommendations

  • Target Marketing:
    • Focus marketing efforts on the 20-50 age group, as it constitutes the majority of the customer base.
  • Data Validation:
    • Ensure accuracy for customers with undefined gender or those above 80 years, as these may indicate data entry errors.

Impact of Promotions on Total Profit

This section investigates the relationship between the Total Value of All Promotions and Total Profit. The scatter plot provides a visual representation of how promotional activities influence customer profitability.

ggplot(df, aes(x = Total_value_of_all_promotions, y = Total_Profit)) +
  geom_point(size = 1, color = colors[1]) +  # Custom color for points from Rosé Pine palette
  labs(
    title = "Impact of Promotions on Total Profit", 
    x = "Total Value of All Promotions", 
    y = "Total Profit"
  ) +
  theme_rose_pine()  # Apply the custom Rosé Pine theme

Observations

  1. Overall Relationship:
    • A positive trend is evident, indicating that higher promotional values are associated with increased total profit.
    • However, the scatterplot suggests diminishing returns as promotions exceed a certain value.
  2. Concentration of Data:
    • A large cluster of data points lies in the lower range of Total Value of All Promotions, suggesting that most customers receive limited promotions.
    • The Total Profit shows significant variability, even for customers with low promotional values.
  3. Outliers:
    • A few customers exhibit extremely high promotional values with varying profit levels. These points could represent special cases like bulk discounts or loyalty rewards.
  4. Potential Non-Linear Pattern:
    • Beyond a certain promotional threshold, the relationship between promotions and profit appears less linear, highlighting the need for further investigation.

Recommendations

  • Optimizing Promotional Strategy:
    • Identify the threshold for diminishing returns and optimize promotional spending to maximize profitability.
  • Customer Segmentation:
    • Analyze segments of customers with high promotions but low profits to improve targeted marketing efforts.
  • Outlier Analysis:
    • Investigate outliers with extremely high promotional values to understand unique cases and refine marketing strategies.

Total Profit Analysis

This section provides insights into total profit distribution across different genders and marketing channel types. The bar plots illustrate the total profit contribution for each category.

# Plot 1: Total Profit by Gender
plot_gender <- ggplot(df, aes(x = factor(Gender), y = Total_Profit, fill = Gender)) +
  geom_bar(stat = "summary", fun = "sum", position = "dodge") +  # Summing the total profit by gender
  labs(
    title = "Total Profit by Gender",
    x = "Gender",
    y = "Total Profit"
  ) +
  theme_rose_pine() + 
  scale_fill_rose_pine() +
  theme(legend.position = "top")  # Move legend to the top

# Plot 2: Total Profit by Marketing Channel Type
plot_marketing_channel <- ggplot(df, aes(x = factor(Marketing_Channel_Type), y = Total_Profit, fill = Marketing_Channel_Type)) +
  geom_bar(stat = "summary", fun = "sum", position = "dodge") +  # Summing total profit by marketing channel
  labs(
    title = "Total Profit by Marketing Channel Type",
    x = "Marketing Channel Type",
    y = "Total Profit"
  ) +
  theme_rose_pine() +
  scale_fill_rose_pine() +
  theme(legend.position = "top")  # Move legend to the top


# Merge the three plots into a grid layout
grid.arrange(plot_gender, plot_marketing_channel, ncol = 2)

Observations

Total Profit by Gender

  1. Male Dominance:
    • Male customers contribute the highest total profit compared to other genders.
    • Female customers show a significant contribution, but it is still much lower than male customers.
    • The “Not Defined” gender category contributes the least to total profit.
  2. Implications:
    • Marketing strategies targeted toward male customers appear to be more effective in driving profits.
    • Potential opportunity exists to improve engagement and profit generation from female customers.

Total Profit by Marketing Channel Type

  1. Top Performing Channels:
    • Direct marketing channels contribute the highest total profit, showcasing the effectiveness of direct customer engagement.
    • Organic search also performs well, indicating the importance of SEO and organic traffic in driving profitability.
  2. Underperforming Channels:
    • Paid social and affiliates channels contribute the least to total profit, suggesting the need to evaluate and optimize strategies for these channels.
  3. Implications:
    • Businesses should prioritize resources on direct and organic channels for maximum profitability.
    • Evaluate the ROI for underperforming channels and refine strategies for better returns.

# Summarize percentage distribution of Gender by Marketing_Channel_Type
gender_channel_distribution <- df %>%
  group_by(Gender, Marketing_Channel_Type) %>%
  summarise(Count = n()) %>%
  group_by(Marketing_Channel_Type) %>%
  mutate(Percentage = (Count / sum(Count)) * 100)

# Pivot data for heatmap
heatmap_data <- gender_channel_distribution %>%
  select(Gender, Marketing_Channel_Type, Percentage) %>%
  pivot_wider(names_from = Marketing_Channel_Type, values_from = Percentage)

# Melt the data for ggplot
melted_data <- melt(heatmap_data, id.vars = "Gender", variable.name = "Marketing_Channel_Type", value.name = "Percentage")

# Plot the heatmap with `scale_fill_gradient2` and Rosé Pine colors
ggplot(melted_data, aes(x = Marketing_Channel_Type, y = Gender, fill = Percentage)) +
  geom_tile() +
  geom_text(aes(label = sprintf("%.2f", Percentage)), size = 6) + # Show percentage with 2 decimals
  scale_fill_gradient2(
    low = colors[2],      # Low color
    mid = colors[1],      # Mid color
    high = colors[3],     # High color
    midpoint = 0.5,        # Assume midpoint at 50% for percentages
    limits = c(0, 100),   # Set limits for percentage
    name = "Percentage"
  ) +
  theme_rose_pine(base_size = 14) +
  labs(
    title = "Gender Distribution by Marketing Channel Type (%)",
    x = "Marketing Channel Type",
    y = "Gender"
  ) +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 19), # Center and bold title
    axis.text.x = element_text(angle = 45, hjust = 1, size = 14), # Rotate x-axis labels
    axis.text.y = element_text(size = 14), # Adjust y-axis text size
    legend.position = "right", # Place the legend on the right
    legend.text = element_text(size = 12), # Adjust legend text size
    legend.title = element_text(size = 13, face = "bold") # Adjust legend title size
  )

Impact of Promotions and Marketing Channels on Total Profit

This section explores the relationship between the total value of promotions and total profit across various marketing channels. The scatter plot highlights the impact of marketing efforts and promotions on customer profitability.

ggplot(df, aes(x = Total_value_of_all_promotions, y = Total_Profit, color = Marketing_Channel_Type)) +
  geom_point(size = 3) +  # Add points with size
  labs(
    title = "Impact of Promotions and Marketing Channels on Total Profit", 
    x = "Total Value of All Promotions", 
    y = "Total Profit"
  ) +
  theme_rose_pine() +  # Apply the custom Rosé Pine theme
  scale_color_rose_pine()  # Apply the Rosé Pine color scale to the color aesthetic

Observations

  1. General Trend:
    • As the total value of promotions increases, there is a corresponding increase in total profit, but the relationship appears non-linear.
    • The majority of data points cluster in the lower ranges of promotion value and profit, indicating many customers generate modest profits with limited promotion expenditure.
  2. Channel-wise Insights:
    • Affiliates and Paid Social tend to have lower profit contributions even with increased promotion values.
    • Direct and Organic Search channels exhibit more consistent profit generation, especially at higher promotion values.
    • Paid Search displays a scattered pattern with some high-profit outliers.
  3. Outliers:
    • Certain customers achieve high profits without substantial promotion value, suggesting high-value customers may require less incentive.
    • Some high promotion values do not correspond to high profits, signaling inefficient promotion strategies.

Recommendations

  • Focus marketing efforts on Direct and Organic Search channels to maximize profitability.
  • Reassess promotion strategies for Affiliates and Paid Social to improve their return on investment (ROI).
  • Investigate high-profit customers with minimal promotions to identify characteristics that can guide customer segmentation and targeted campaigns.

Locations by Total Profit and Gender

This visualization presents the distribution of total profit across different locations, segmented by gender (Female, Male, and Not Defined). The analysis focuses on identifying geographical profitability trends.

# Summarize the total profit by Location
location_rev <- df %>%
  group_by(Location) %>%
  summarise(Total_Profit = sum(Total_Profit, na.rm = TRUE)) %>%
  arrange(Total_Profit) %>%
  mutate(Location = factor(Location, levels = .$Location))

# Summarize total profit by Location and Gender
location_gender_rev <- df %>%
  group_by(Location, Gender) %>%
  summarise(Total_Profit = sum(Total_Profit, na.rm = TRUE)) %>%
  ungroup() %>%
  mutate(Location = factor(Location, levels = location_rev$Location))

# Plot the data for the top 20 locations by Total_Profit and Gender
ggplot(location_gender_rev, aes(Location, Total_Profit, fill = Gender)) +
  geom_bar(stat = "identity", position = "dodge") +
  coord_flip() +
  facet_wrap(~ Gender) +
  theme_rose_pine() +  # Apply the custom theme
  scale_fill_rose_pine() +  # Apply the fill color scale
  labs(title = "Locations by Total Profit and Gender", x = "Location", y = "Total Profit") +
  theme(legend.position = "top")  # Move the legend to the top

Observations

  1. Overall Insights:
    • Dublin significantly outperforms all other locations in total profit contributions, irrespective of gender.
    • Locations such as Cork, Galway, and Limerick follow Dublin, but their profit contributions are considerably lower.
  2. Gender-Based Distribution:
    • Male customers dominate the total profit contributions across almost all locations, highlighting their significant role in revenue generation.
    • Female customers show notable contributions in Dublin and a few other top cities but lag behind male customers overall.
    • The Not Defined gender group has minimal profit contributions and is mostly concentrated in a few locations.
  3. Geographical Distribution:
    • A long tail of smaller towns contributes marginally to total profit. These locations could indicate untapped potential or low market penetration.
    • High concentration of profit in major cities like Dublin suggests a strong urban customer base.

Recommendations

  • Focus marketing and customer engagement efforts on high-profit cities such as Dublin, Cork, and Galway.
  • Develop strategies to increase female customer engagement in smaller towns and cities.
  • Investigate the “Not Defined” gender group to understand its characteristics and potential for growth.
  • Explore opportunities in smaller towns with low profit contributions to expand market reach.

Impact of Promotions on Total Profit

This scatter plot visualizes the relationship between the Total Value of All Promotions and the Total Profit, showcasing how promotional efforts influence overall profitability.

# Create scatter plot with regression line
ggplot(df, aes(x = Total_value_of_all_promotions, y = Total_Profit)) +
  geom_point(color = colors[1]) +  # Scatter plot with points colored in orange
  geom_smooth(method = "lm", color = colors[2], se = FALSE) +  # Add regression line
  labs(
    title = "Impact of Promotions on Total Profit",
    x = "Total Value of All Promotions",
    y = "Total Profit"
  ) +
  theme_rose_pine()  # Apply Rose Pine theme

Observations

  1. Positive Correlation:
    • The scatter plot demonstrates a positive correlation between the total value of promotions and total profit. As the promotional value increases, the profit generally rises.
    • The fitted trend line further supports this relationship, indicating that promotions effectively contribute to profit generation.
  2. Diminishing Returns:
    • Beyond a certain promotional value (~50 units), the incremental increase in profit becomes less pronounced.
    • This suggests diminishing returns on investment in promotions, where further spending may yield marginal profit gains.
  3. High Variability:
    • A high concentration of points at lower promotional values (~0 to 20 units) reflects variability in profits, indicating that other factors may influence customer behavior and profitability.

Recommendations

  • Optimize Promotional Spending:
    • Focus on promotional values that yield the highest return on investment, potentially within the range of 20 to 50 units.
  • Analyze High Variability:
    • Investigate factors contributing to the variability in profit for lower promotional values to refine strategies.
  • Targeted Promotions:
    • Design promotions targeted at customer segments most responsive to increased spending.

4. Outlier Detection Methods

This document demonstrates how to detect outliers using Z-Score, Modified Z-Score, and IQR methods on a small dataset. We will calculate the outliers step-by-step for a simple 5-row dataset.


Example Dataset

We will use the following dataset:

Row Value
1 10
2 12
3 14
4 100
5 16

Z-Score Method

Formula

\[ Z = \frac{(X - \mu)}{\sigma} \]

Where: - \(X\): The value. - \(\mu\): Mean of the dataset. - \(\sigma\): Standard deviation of the dataset.

Steps

  1. Compute the Mean (\(\mu\)): \[ \mu = \frac{10 + 12 + 14 + 100 + 16}{5} = 30.4 \]

  2. Compute the Standard Deviation (\(\sigma\)): \[ \sigma = \sqrt{\frac{\sum{(X - \mu)^2}}{n}} \] Substituting values: \[ \sigma = \sqrt{\frac{(10-30.4)^2 + (12-30.4)^2 + (14-30.4)^2 + (100-30.4)^2 + (16-30.4)^2}{5}} \] \[ \sigma \approx 38.89 \]

  3. Compute Z-Scores: Using \(Z = \frac{(X - \mu)}{\sigma}\):

    • \(10: Z \approx -0.524\)
    • \(12: Z \approx -0.473\)
    • \(14: Z \approx -0.423\)
    • \(100: Z \approx 1.792\)
    • \(16: Z \approx -0.372\)
  4. Threshold: Outliers are values with \(|Z| > 3\).


Modified Z-Score Method

Formula

\[ MZ = 0.6745 \cdot \frac{(X - \text{median})}{MAD} \]

Where: - \(X\): The value. - \(\text{median}\): Median of the dataset. - \(MAD\): Median Absolute Deviation: \[ MAD = \text{Median}(|X_i - \text{median}|) \]

Steps

  1. Compute the Median: \[ \text{Median} = 14 \]

  2. Compute MAD: \[ |10-14| = 4, \, |12-14| = 2, \, |14-14| = 0, \, |100-14| = 86, \, |16-14| = 2 \] Median of absolute deviations: \[ MAD = \text{Median}([4, 2, 0, 86, 2]) = 2 \]

  3. Compute Modified Z-Scores: Using \(MZ = 0.6745 \cdot \frac{(X - \text{median})}{MAD}\):

    • \(10: MZ \approx -1.349\)
    • \(12: MZ \approx -0.675\)
    • \(14: MZ \approx 0\)
    • \(100: MZ \approx 28.916\)
    • \(16: MZ \approx 0.675\)
  4. Threshold: Outliers are values with \(|MZ| > 3.5\). Hence, \(100\) is an outlier.


IQR Method

Formula

Outliers are values outside: \[ [\text{Q1} - 1.5 \cdot \text{IQR}, \text{Q3} + 1.5 \cdot \text{IQR}] \] Where: - \(Q1\): 25th percentile. - \(Q3\): 75th percentile. - \(\text{IQR} = Q3 - Q1\).

Steps

  1. Compute Quartiles:

    • Sorted dataset: \([10, 12, 14, 16, 100]\).
    • \(Q1 = 12, Q3 = 16\).
  2. Compute IQR: \[ \text{IQR} = Q3 - Q1 = 16 - 12 = 4 \]

  3. Compute Outlier Bounds:

    • Lower Bound: \[ \text{Lower Bound} = Q1 - 1.5 \cdot \text{IQR} = 12 - 1.5 \cdot 4 = 6 \]
    • Upper Bound: \[ \text{Upper Bound} = Q3 + 1.5 \cdot \text{IQR} = 16 + 1.5 \cdot 4 = 22 \]
  4. Identify Outliers:

    • Values outside \([6, 22]\) are outliers. In this case, \(100\) is an outlier.

Summary Table

Row Value Z-Score Modified Z-Score IQR Outlier
1 10 -0.524 -1.349 No
2 12 -0.473 -0.675 No
3 14 -0.423 0.000 No
4 100 1.792 28.916 Yes
5 16 -0.372 0.675 No

Key Observations

  • Z-Score: Detects no outliers (\(|Z| > 3\)).
  • Modified Z-Score: Detects \(100\) as an outlier (\(|MZ| > 3.5\)).
  • IQR Method: Detects \(100\) as an outlier (outside \([6, 22]\)).

Conclusion

  • Use IQR for simple EDA or robust cleaning.
  • Use Modified Z-Score for skewed datasets or small sample sizes.
  • Use Z-Score for symmetric, normal distributions.
# Filter numeric columns only from selected_cols
numeric_data <- df %>% select(all_of(selected_cols)) %>% select(where(is.numeric))

## 1. Z-SCORE METHOD
z_score_outliers <- numeric_data %>%
  summarise(across(everything(), ~ {
    z_scores <- scale(.)  # Standardize data
    sum(abs(z_scores) > 3, na.rm = TRUE)  # Count outliers
  })) %>%
  pivot_longer(everything(), names_to = "Column", values_to = "Z_Score_Outliers")

## 2. MODIFIED Z-SCORE METHOD
modified_z_outliers <- numeric_data %>%
  summarise(across(everything(), ~ {
    median_val <- median(., na.rm = TRUE)
    mad_val <- mad(., constant = 1.4826, na.rm = TRUE)  # MAD-based scale
    modified_z <- 0.6745 * (.-median_val) / mad_val
    sum(abs(modified_z) > 3.5, na.rm = TRUE)  # Count robust outliers
  })) %>%
  pivot_longer(everything(), names_to = "Column", values_to = "Modified_Z_Outliers")

## 3. IQR METHOD
iqr_outliers <- numeric_data %>%
  summarise(across(everything(), ~ {
    Q1 <- quantile(., 0.25, na.rm = TRUE)
    Q3 <- quantile(., 0.75, na.rm = TRUE)
    IQR <- Q3 - Q1
    lower_bound <- Q1 - 1.5 * IQR
    upper_bound <- Q3 + 1.5 * IQR
    sum(. < lower_bound | . > upper_bound, na.rm = TRUE)  # Count IQR outliers
  })) %>%
  pivot_longer(everything(), names_to = "Column", values_to = "IQR_Outliers")

## Combine Results for All Methods
outlier_summary <- z_score_outliers %>%
  full_join(modified_z_outliers, by = "Column") %>%
  full_join(iqr_outliers, by = "Column")

# Print the full summary
print(outlier_summary)

5. Handling Outliers

When dealing with outlier removal, two popular IQR-based methods are:

  1. Column-Wise Filtering: Removes outliers for each column individually.
  2. Row-Wise Filtering: Removes entire rows if any column contains an outlier.

This document explores their characteristics, advantages, disadvantages, and use cases.


Column-Wise Filtering

Column-Wise Filtering is a less aggressive approach to outlier removal. It detects and removes outliers from each column individually while retaining rows that are valid for other columns. This method is especially useful for exploratory data analysis (EDA), where retaining as much data as possible is essential.

remove_outliers_columnwise <- function(data, cols) {
  for (col in cols) {
    Q1 <- quantile(data[[col]], 0.25, na.rm = TRUE)
    Q3 <- quantile(data[[col]], 0.75, na.rm = TRUE)
    IQR <- Q3 - Q1
    lower_bound <- Q1 - 1.5 * IQR
    upper_bound <- Q3 + 1.5 * IQR
    data <- data %>% filter(data[[col]] >= lower_bound & data[[col]] <= upper_bound)
  }
  return(data)
}

Example Execution

cleaned_df_columnwise <- remove_outliers_columnwise(df, selected_cols)
cat("Original Rows:", nrow(df))
Original Rows: 30591
cat("Cleaned Rows:", nrow(cleaned_df_columnwise))
Cleaned Rows: 24180

Characteristics of Column-Wise Filtering

How It Works

  • Outliers are detected and removed for each column independently.
  • Rows are retained unless flagged as an outlier in the current column being processed.

Advantages

  • Retains more data overall, as rows valid in other columns are not removed.
  • Less aggressive, making it suitable for exploratory data analysis (EDA) or initial cleaning steps.

Disadvantages

  • Inconsistent cleaning: A row flagged as an outlier in one column but valid in another remains, potentially leading to inconsistent results.

Row-Wise Filtering

remove_outliers_iqr <- function(data, cols) {
  data %>%
    filter(across(all_of(cols), ~ {
      Q1 <- quantile(., 0.25, na.rm = TRUE)
      Q3 <- quantile(., 0.75, na.rm = TRUE)
      IQR <- Q3 - Q1
      lower_bound <- Q1 - 1.5 * IQR
      upper_bound <- Q3 + 1.5 * IQR
      . >= lower_bound & . <= upper_bound
    }))
}

Example Execution

# Example: Applying Row-Wise Filtering
cleaned_df_rowwise <- remove_outliers_iqr(df, selected_cols)

# Output: Before and After Row Counts
cat("Original Rows:", nrow(df))
Original Rows: 30591
cat("Cleaned Rows:", nrow(cleaned_df_rowwise))
Cleaned Rows: 25869

Characteristics of Row-Wise Filtering

How It Works

  • Outliers are detected across all selected columns at the same time.
  • If any column in a row contains an outlier, the entire row is removed.

Advantages

  • Ensures consistent cleaning across all selected columns.
  • Suitable for machine learning pipelines or strict statistical analysis, where clean and complete data is crucial.

Disadvantages

  • More aggressive: This approach often leads to significant data loss when variability exists across multiple columns.

Which One Should You Use?

Selecting the appropriate outlier removal method depends on your goal. Here’s a straightforward guide to help you decide.


If Your Goal is Exploratory Data Analysis (EDA) or You Want to Retain as Much Data as Possible:

  • Use: remove_outliers_columnwise
  • Why Choose This Method?
    • This approach is less aggressive, removing outliers column by column.
    • Rows that are valid in other columns remain intact, allowing you to retain more of your dataset.
    • Perfect for initial data cleaning or early-stage exploratory data analysis (EDA), where preserving data for a broader overview is essential.

If Your Goal is Machine Learning or Statistical Modeling, Where Consistency Across Rows is Critical:

  • Use: remove_outliers_iqr
  • Why Choose This Method?
    • This method is more stringent, removing entire rows if any column contains an outlier.
    • Ensures that your dataset is consistent and free from outliers in the specified columns.
    • Ideal for machine learning pipelines or statistical modeling, where clean and consistent data is crucial for accurate results.

Quick Comparison

Goal Recommended Method Why?
Exploratory Data Analysis (EDA) remove_outliers_columnwise Retains more data for exploration.
Machine Learning/Modeling remove_outliers_iqr Ensures strict consistency across rows.

Final Thoughts

  • Use Column-Wise Filtering (remove_outliers_columnwise) when exploring data and aiming to preserve as much information as possible.
  • Use Row-Wise Filtering (remove_outliers_iqr) when consistency across rows is critical for downstream tasks like modeling or analysis.

Note: Always evaluate the impact of your chosen method on the dataset to ensure it aligns with your goals.


Violin Plots Column-Wise Filtering

Violin plots provide a detailed representation of the distribution of data, combining the information of a box plot and a density plot. This visualization is particularly useful for identifying patterns, spread, and potential outliers within a dataset.

The following violin plots display the distributions of key features in the dataset after applying column-wise outlier removal using the IQR method. The selected features include:

  1. First Order Profit
  2. Subsequent Order Profit
  3. Subsequent Orders Count
  4. Total Value of All Promotions
  5. Age
  6. Total Profit

# Step 3: Generate Violin Plots Dynamically
violin_plot_list <- lapply(seq_along(selected_cols), function(i) {
  col_name <- selected_cols[i]
  formatted_title <- tools::toTitleCase(gsub("_", " ", col_name))
  
  ggplot(cleaned_df_columnwise, aes(x = factor(1), y = .data[[col_name]])) +
    geom_violin(fill = colors[i], color = colors[i], amount = 0.2) +  # Dynamic colors
    labs(
      title = paste("", formatted_title),
      x = "", 
      y = "Value"
    ) +
    theme_rose_pine() +  # Apply Rose Pine theme
    theme(
      axis.text.x = element_blank(),  # Remove x-axis text
      axis.ticks.x = element_blank(),  # Remove x-axis ticks
      plot.title = element_text(size = 11, face = "bold")  # Title formatting
    )
})

# Step 4: Combine All Violin Plots into a Grid Layout
grid.arrange(grobs = violin_plot_list, ncol = length(selected_cols))

Observations

First Order Profit

  • The majority of the values are concentrated within the lower range, indicating most customers’ first orders yield relatively low profit.
  • A small tail suggests a few customers have significantly higher profits.

Subsequent Order Profit

  • Similar to the first order, the density is concentrated at the lower end with fewer customers contributing to higher profits.

Subsequent Orders Count

  • Most customers have placed a limited number of subsequent orders.
  • The distribution reveals a small number of customers with many subsequent orders.

Total Value of All Promotions

  • Promotions are concentrated at low values, suggesting limited usage or impact for the majority of customers.
  • The density plot shows a sharp decline as the value increases.

Age

  • Age distribution is fairly uniform in the middle range (20–40 years), tapering off for younger and older customers.

Total Profit

  • The overall profit is primarily low, with a few customers contributing to high profits.

Advantages of Violin Plots

  • Combines the features of a box plot and density plot, making it easier to identify the spread and density of data.
  • Highlights potential outliers visually while retaining the overall shape of the distribution.

Limitations

  • Does not provide a direct count of outliers.
  • Interpretation can be challenging for features with highly skewed distributions.

Conclusion

The violin plots offer a comprehensive overview of the cleaned data distributions, highlighting the concentration of values, the spread, and the presence of potential outliers. These visualizations serve as a valuable tool for exploratory data analysis (EDA) and preparing data for further modeling.

# Subset the cleaned dataset to the selected columns
correlation_data <- cleaned_df_columnwise[, selected_cols]

# Compute the correlation matrix
correlation_matrix <- cor(correlation_data, use = "pairwise.complete.obs")

# Melt the correlation matrix for ggplot
correlation_melted <- melt(correlation_matrix)

# Generate the correlation heatmap
ggplot(data = correlation_melted, aes(x = Var1, y = Var2, fill = value)) +
  geom_tile() + # Add white border for tiles
  geom_text(aes(label = round(value, 2)), size = 6) + # Add correlation coefficients
  scale_fill_gradient2(
    low = colors[3], 
    high = colors[1], 
    mid = colors[2], 
    midpoint = 0, 
    limits = c(-1, 1),
    guide = guide_colorbar(
      title = "",
      barwidth = 40, # Make the color bar wider
      barheight = 1  # Adjust the height of the bar
    )
  ) +
  theme_rose_pine() + # Apply the Rosé Pine theme
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 16),
    axis.text.y = element_text(size = 16),
    plot.title = element_text(size = 20, face = "bold", hjust = 0.28), # Center and adjust title size
    axis.title = element_blank(), # Remove axis titles
    panel.grid = element_blank(), # Remove grid lines
    legend.position = "top",
    legend.text = element_text(size = 11), # Adjust legend text size
  )+
  ggtitle("Correlation Matrix") # Add plot title

# Select categorical columns
categorical_cols <- c("Gender", "Location", "Contact_Allowed", "Marketing_Channel_Type")

# Subset the dataset
categorical_data <- cleaned_df_columnwise[, categorical_cols]

# Define a function to compute Cramér's V for a pair of categorical features
cramers_v_matrix <- function(data) {
  n <- ncol(data)
  matrix <- matrix(NA, n, n, dimnames = list(names(data), names(data)))
  for (i in seq_len(n)) {
    for (j in seq_len(n)) {
      if (i <= j) {
        matrix[i, j] <- cramersV(data[[i]], data[[j]], simulate.p.value = TRUE)
      } else {
        matrix[i, j] <- matrix[j, i]
      }
    }
  }
  return(matrix)
}

# Compute the Cramér's V matrix
cramers_v <- cramers_v_matrix(categorical_data)

# Melt the matrix for ggplot
melted_cramers_v <- melt(cramers_v, na.rm = TRUE)

# Generate the heatmap
ggplot(data = melted_cramers_v, aes(x = Var1, y = Var2, fill = value)) +
  geom_tile() + # Add white borders for tiles
  geom_text(aes(label = round(value, 2)), size = 6) + # Add Cramér's V values
  scale_fill_gradient2(
    low = colors[2], 
    high = colors[1], 
    mid = colors[3], 
    midpoint = 0.5,
    limits = c(0, 1),
    guide = guide_colorbar(
      title = "",
      barwidth = 30, # Adjust color bar width
      barheight = 1  # Adjust color bar height
    )
  ) +
  theme_rose_pine(base_size = 14) + # Apply Rosé Pine theme
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 16),
    axis.text.y = element_text(size = 16),
    plot.title = element_text(size = 20, face = "bold", hjust = 0.5), # Center the title
    axis.title = element_blank(), # Remove axis titles
    panel.grid = element_blank(), # Remove grid lines
    legend.position = "top",
    legend.text = element_text(size = 12), # Adjust legend text size
    legend.title = element_text(size = 14, face = "bold")
  ) +
  ggtitle("Cramér's V Correlation Matrix for Categorical Features") # Add plot title

ggpairs(
  data = cleaned_df_columnwise[, selected_cols],
  mapping = aes(color = "Total_Profit"), # Replace with a relevant column if needed
  lower = list(
    continuous = wrap("smooth", color = colors[2], size = 0.8),
    combo = wrap("facetdensity", alpha = 0.7, fill = colors[3])
  ),
  upper = list(
    continuous = wrap("cor", size = 6, color = rose_pine_colors$text)
  ),
  diag = list(
    continuous = wrap("densityDiag", fill = colors[1], alpha = 0.7)
  )
) +
  theme_rose_pine(base_size = 12) + # Apply the Rosé Pine theme
  theme(
    plot.title = element_text(size = 30, face = "bold"), # Title font size
    axis.text = element_text(size = 22), # Axis text size
    axis.title = element_text(size = 22), # Axis title size
    strip.text = element_text(size = 14) # Size of facet labels
  ) +
  labs(
    title = "Pairplot with Correlation"
  )

# Parse Registration_Date as datetime
cleaned_df_columnwise$Registration_Date <- mdy_hm(cleaned_df_columnwise$Registration_Date)

# Extract the month and year
cleaned_df_columnwise <- cleaned_df_columnwise %>%
  mutate(Month = month(Registration_Date, label = TRUE, abbr = TRUE)) # e.g., Jan, Feb

monthly_profit <- cleaned_df_columnwise %>%
  group_by(Month) %>%
  summarise(Total_Profit = sum(Total_Profit, na.rm = TRUE)) %>%
  arrange(Month)

ggplot(monthly_profit, aes(x = Month, y = Total_Profit, group = 1)) +
  geom_line(color = colors[1], size = 1.2, linetype="twodash") + # Line with custom color
  geom_point(color = colors[2], size = 3) + # Points on the line
  labs(
    title = "Monthly Total Profit in 2013",
    x = "Month",
    y = "Total Profit"
  ) +
  theme_rose_pine(base_size = 15) +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"), # Centered title
    axis.text.x = element_text(size = 14),
    axis.text.y = element_text(size = 14)
  )

# Group data by Marketing_Channel_Type and Month, then calculate the total profit
monthly_profit_by_channel <- cleaned_df_columnwise %>%
  group_by(Month, Marketing_Channel_Type) %>%
  summarise(Total_Profit = sum(Total_Profit, na.rm = TRUE)) %>%
  arrange(Month)

# Check the unique marketing channels and colors
unique_channels <- unique(monthly_profit_by_channel$Marketing_Channel_Type)

# Ensure the colors match the unique channels
colors_for_channels <- setNames(colors[1:length(unique_channels)], unique_channels)

# Plot with color mapping
ggplot(monthly_profit_by_channel, aes(x = Month, y = Total_Profit, color = Marketing_Channel_Type, group = Marketing_Channel_Type)) +
  geom_line(size = 1.2, linetype="twodash") + # Line for each marketing channel
  geom_point(size = 3) + # Points on the lines
  scale_color_manual(values = colors_for_channels) + # Map colors to channels
  labs(
    title = "Monthly Total Profit by Marketing Channel in 2013",
    x = "Month",
    y = "Total Profit",
    color = "Marketing Channel" # Legend title
  ) +
  theme_rose_pine(base_size = 15) + # Apply Rosé Pine theme
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"), # Centered title
    axis.text.x = element_text(size = 16),
    axis.text.y = element_text(size = 16),
    legend.position = "right", # Legend position
    legend.text = element_text(size = 14), # Legend text size
    legend.title = element_text(size = 16, face = "bold") # Legend title size
  )

5. Conclusion

This report highlights the key metrics, major data points, and correlations from the dataset. The visualization provides a clear picture of the characteristics of different attributes.

LS0tCnRpdGxlOiAiQ3VzdG9tZXIgUHJvZml0YWJpbGl0eSBhbmQgTWFya2V0aW5nIEFuYWx5c2lzIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY3NzOiAuLi9hc3NldHMvY3NzL3N0eWxlcy5jc3MKICAgIGluY2x1ZGVzOgogICAgICBiZWZvcmVfYm9keTogLi4vdGVtcGxhdGVzL2dpdGh1Yi5odG1sCiAgICAgIGFmdGVyX2JvZHk6IC4uL3RlbXBsYXRlcy9mb290ZXIuaHRtbAplZGl0b3Jfb3B0aW9uczogIAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgMS4gSW50cm9kdWN0aW9uCgpJbiB0b2RheSdzIGNvbXBldGl0aXZlIG1hcmtldCwgdW5kZXJzdGFuZGluZyBjdXN0b21lciBiZWhhdmlvciBpcyBjcnVjaWFsIGZvciBkcml2aW5nIHByb2ZpdGFiaWxpdHkgYW5kIGxvbmctdGVybSBzdWNjZXNzLiBCeSBhbmFseXppbmcgdHJhbnNhY3Rpb24gZGF0YSwgYnVzaW5lc3NlcyBjYW4gZ2FpbiBpbnNpZ2h0cyBpbnRvIGtleSBmYWN0b3JzIHN1Y2ggYXMgKipjdXN0b21lciBkZW1vZ3JhcGhpY3MqKiwgKiptYXJrZXRpbmcgY2hhbm5lbCBwZXJmb3JtYW5jZSoqLCBhbmQgKipwcm9maXRhYmlsaXR5IHRyZW5kcyoqLiBUaGVzZSBpbnNpZ2h0cyBlbmFibGUgYnVzaW5lc3NlcyB0byBtYWtlIGRhdGEtZHJpdmVuIGRlY2lzaW9ucyB0byBlbmhhbmNlIGN1c3RvbWVyIHJldGVudGlvbiwgb3B0aW1pemUgbWFya2V0aW5nIHN0cmF0ZWdpZXMsIGFuZCBpbmNyZWFzZSBvdmVyYWxsIHJldmVudWUuCgpUaGlzIHByb2plY3QgbGV2ZXJhZ2VzIGEgY29tcHJlaGVuc2l2ZSBkYXRhc2V0IHRvIGV4cGxvcmUgdGhlc2UgZHluYW1pY3MsIGZvY3VzaW5nIG9uIGFjdGlvbmFibGUgb3V0Y29tZXMgYW5kIHJlY29tbWVuZGF0aW9ucy4KCi0tLQoKIyMgRGF0YXNldCBDb2x1bW4gSW5mb3JtYXRpb24KCkJlbG93IGlzIHRoZSBkZXNjcmlwdGlvbiBvZiBlYWNoIGNvbHVtbiBpbiB0aGUgZGF0YXNldDoKCnwgU04gIHwgQ29sdW1uIE5hbWUgICAgICAgICAgICAgICAgICAgIHwgQ29sdW1uIERlc2NyaXB0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwtLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfAp8IDEgICB8IEN1c3RvbWVyX0lEICAgICAgICAgICAgICAgICAgICB8IFVuaXF1ZSBpZGVudGlmaWVyIGZvciBlYWNoIGN1c3RvbWVyICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAyICAgfCBHZW5kZXIgICAgICAgICAgICAgICAgICAgICAgICAgIHwgR2VuZGVyIG9mIHRoZSBjdXN0b21lciAoTWFsZS9GZW1hbGUpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgMyAgIHwgRGF0ZV9PZl9CaXJ0aCAgICAgICAgICAgICAgICAgICB8IEJpcnRoIGRhdGUgb2YgdGhlIGN1c3RvbWVyIGluIE1NL0REL1lZWVkgZm9ybWF0ICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IDQgICB8IExvY2F0aW9uICAgICAgICAgICAgICAgICAgICAgICAgfCBMb2NhdGlvbiAoY2l0eSkgb2YgdGhlIGN1c3RvbWVyICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCA1ICAgfCBDb250YWN0X0FsbG93ZWQgICAgICAgICAgICAgICAgIHwgV2hldGhlciB0aGUgY3VzdG9tZXIgaGFzIGFsbG93ZWQgdG8gYmUgY29udGFjdGVkIChZL04pICAgICAgICAgICAgICAgICAgICAgfAp8IDYgICB8IFJlZ2lzdHJhdGlvbl9EYXRlICAgICAgICAgICAgICAgfCBEYXRlIGFuZCB0aW1lIHdoZW4gdGhlIGN1c3RvbWVyIHJlZ2lzdGVyZWQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCA3ICAgfCBNYXJrZXRpbmdfQ2hhbm5lbF9UeXBlICAgICAgICAgIHwgVGhlIG1hcmtldGluZyBjaGFubmVsIHRocm91Z2ggd2hpY2ggdGhlIGN1c3RvbWVyIHdhcyBhY3F1aXJlZCAgICAgICAgICAgICB8CnwgOCAgIHwgRmlyc3RfT3JkZXJfUHJvZml0ICAgICAgICAgICAgICB8IFByb2ZpdCBmcm9tIHRoZSBjdXN0b21lcuKAmXMgZmlyc3Qgb3JkZXIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgOSAgIHwgU3Vic2VxdWVudF9PcmRlcl9Qcm9maXQgICAgICAgICB8IFByb2ZpdCBmcm9tIGFsbCBzdWJzZXF1ZW50IG9yZGVycyBvZiB0aGUgY3VzdG9tZXIgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IDEwICB8IFN1YnNlcXVlbnRfT3JkZXJzX0NvdW50ICAgICAgICAgfCBOdW1iZXIgb2Ygc3Vic2VxdWVudCBvcmRlcnMgcGxhY2VkIGJ5IHRoZSBjdXN0b21lciAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAxMSAgfCBUb3RhbF92YWx1ZV9vZl9hbGxfcHJvbW90aW9ucyAgIHwgVG90YWwgdmFsdWUgb2YgYWxsIHByb21vdGlvbnMgdGhlIGN1c3RvbWVyIHJlY2VpdmVkICAgICAgICAgICAgICAgICAgICAgICB8CnwgMTIgIHwgQWdlICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IEFnZSBvZiB0aGUgY3VzdG9tZXIgKGNhbGN1bGF0ZWQgZnJvbSBEYXRlX09mX0JpcnRoKSAgICAgICAgICAgICAgICAgICAgICAgfAp8IDEzICB8IFRvdGFsX1Byb2ZpdCAgICAgICAgICAgICAgICAgICAgfCBUb3RhbCBwcm9maXQgZ2VuZXJhdGVkIGZyb20gdGhlIGN1c3RvbWVyIChGaXJzdCArIFN1YnNlcXVlbnQpICAgICAgICAgICAgIHwKCi0tLQoKIyMgR29hbAoKVGhlIHByaW1hcnkgZ29hbCBvZiB0aGlzIHByb2plY3QgaXMgdG8gYW5hbHl6ZSAqKmN1c3RvbWVyIGJlaGF2aW9yKiogYW5kICoqcHJvZml0YWJpbGl0eSoqIGluIHJlbGF0aW9uIHRvIHZhcmlvdXMgbWFya2V0aW5nIGNoYW5uZWxzLiBCeSBsZXZlcmFnaW5nIHRoZSBkYXRhc2V0LCB3aGljaCBpbmNsdWRlcyBlc3NlbnRpYWwgY3VzdG9tZXIgYW5kIHRyYW5zYWN0aW9uIGRldGFpbHMsIHdlIGFpbSB0byB1bmNvdmVyIGZhY3RvcnMgdGhhdCBkcml2ZSAqKkN1c3RvbWVyIExpZmV0aW1lIFZhbHVlIChDTFYpKiogYW5kIG1lYXN1cmUgdGhlIGVmZmVjdGl2ZW5lc3Mgb2YgZGlmZmVyZW50IG1hcmtldGluZyBzdHJhdGVnaWVzLgoKVGhyb3VnaCB0aGlzIGFuYWx5c2lzLCB0aGUgYnVzaW5lc3Mgd2lsbCBnYWluIGFjdGlvbmFibGUgaW5zaWdodHMgdG86Ci0gT3B0aW1pemUgbWFya2V0aW5nIGVmZm9ydHMuCi0gSW1wcm92ZSBjdXN0b21lciByZXRlbnRpb24uCi0gSW5jcmVhc2Ugb3ZlcmFsbCBwcm9maXRzLgoKLS0tCgojIyBEZWxpdmVyYWJsZXMKCiMjIyAxLiAqKkRhdGEgQ2xlYW5pbmcgYW5kIFRyYW5zZm9ybWF0aW9uKioKLSBDbGVhbiBhbmQgcHJlcHJvY2VzcyB0aGUgZGF0YXNldCBieToKICAtIEhhbmRsaW5nIG1pc3NpbmcgdmFsdWVzLgogIC0gRGV0ZWN0aW5nIGFuZCBhZGRyZXNzaW5nIG91dGxpZXJzLgogIC0gQ29udmVydGluZyBjb2x1bW5zIHRvIGFwcHJvcHJpYXRlIGZvcm1hdHMgZm9yIGFuYWx5c2lzLgoKLS0tCgojIyMgMi4gKipFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIChFREEpKioKLSBQZXJmb3JtIHN0YXRpc3RpY2FsIGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9ucyB0bzoKICAtIElkZW50aWZ5IHRyZW5kcywgcmVsYXRpb25zaGlwcywgYW5kIHBhdHRlcm5zLgogIC0gSGlnaGxpZ2h0IGFub21hbGllcyBvciB1bnVzdWFsIGJlaGF2aW9ycyBpbiB0aGUgZGF0YS4KICAKIyMjIDMuICoqT3V0bGllciBEZXRlY3Rpb24qKgotICoqUHVycG9zZSoqOiBEZXRlY3QgdW51c3VhbCBwYXR0ZXJucyBpbiBjdXN0b21lciB0cmFuc2FjdGlvbnMsIHN1Y2ggYXM6CiAgLSAqKkV4Y2VwdGlvbmFsbHkgaGlnaCBwcm9maXRzKioKICAtICoqUHJvbW90aW9ucyBvciBkaXNjb3VudHMqKgogIC0gKipVbnVzdWFsIG9yZGVyIGJlaGF2aW9yKioKLSAqKkdvYWwqKjogVW5kZXJzdGFuZCBleGNlcHRpb25hbCBjYXNlcywgcmVmaW5lIG1hcmtldGluZyBzdHJhdGVnaWVzLCBhbmQgbWFuYWdlIGJvdGggaGlnaC12YWx1ZSBhbmQgbG93LXZhbHVlIGN1c3RvbWVycyBlZmZlY3RpdmVseS4KCi0tLQoKIyMgU3VtbWFyeQoKVGhpcyBhbmFseXNpcyB3aWxsIGVtcG93ZXIgdGhlIGJ1c2luZXNzIHRvOgotIFVuZGVyc3RhbmQgaXRzIGN1c3RvbWVyIGJhc2UgbW9yZSBkZWVwbHkuCi0gVGFpbG9yIG1hcmtldGluZyBzdHJhdGVnaWVzIHRvIG1heGltaXplIGVuZ2FnZW1lbnQgYW5kIHByb2ZpdGFiaWxpdHkuCi0gUmVmaW5lIGNhbXBhaWducyB0byBpbXByb3ZlICoqY3VzdG9tZXIgc2F0aXNmYWN0aW9uKiogYW5kICoqbG9uZy10ZXJtIGdyb3d0aCoqLgoKQnkgZm9jdXNpbmcgb24gdGhlc2Ugb2JqZWN0aXZlcywgdGhlIGJ1c2luZXNzIGNhbiB0dXJuIGRhdGEtZHJpdmVuIGluc2lnaHRzIGludG8gaW1wYWN0ZnVsIGFjdGlvbnMuCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQojIExvYWQgcmVxdWlyZWQgbGlicmFyaWVzCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShnZ3RoZW1lcykgIyBGb3IgY3VzdG9tIHRoZW1lcwpsaWJyYXJ5KGxzcikgIyBGb3IgQ3JhbcOpcidzIFYKbGlicmFyeShyb3NlcGluZVRoZW1lKQoKIyBFeHRyYWN0IHRoZSBmaXJzdCBuIGFjY2VudHMgZnJvbSByb3NlX3BpbmVfY29sb3JzCmdldF9yb3NlX3BpbmVfY29sb3JzIDwtIGZ1bmN0aW9uKG4pIHsKICBhY2NlbnRzIDwtIGdyZXAoImFjY2VudCIsIG5hbWVzKHJvc2VfcGluZV9jb2xvcnMpLCB2YWx1ZSA9IFRSVUUpICMgR2V0IGFsbCBhY2NlbnQga2V5cwogIHVubGlzdChyb3NlX3BpbmVfY29sb3JzW2FjY2VudHNdKVsxOm5dICAjIEV4dHJhY3QgZmlyc3QgbiBhY2NlbnQgdmFsdWVzCn0KYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgMi4gRGF0YSBFeHBsb3JhdGlvbiBhbmQgUHJlcHJvY2Vzc2luZwoKCiMjIExvYWRpbmcgYW5kIFByZXZpZXdpbmcgdGhlIERhdGEKCkluIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCBsb2FkIHRoZSBkYXRhc2V0IGZyb20gYSBDU1YgZmlsZSBhbmQgcHJldmlldyB0aGUgZmlyc3QgZmV3IHJvd3MuCgpgYGB7cn0KIyBMb2FkIHRoZSBDU1YgZmlsZQpkZiA8LSByZWFkLmNzdigiLi4vZGF0YS9tYXJrZXRpbmdfZGF0YS5jc3YiKQpgYGAKCioqRGlzcGxheWluZyB0aGUgRmlyc3QgRmV3IFJvd3Mgb2YgdGhlIERhdGFzZXQqKgoKYGBge3J9CiMgRGlzcGxheSB0aGUgZmlyc3QgZmV3IHJvd3MKaGVhZChkZikKYGBgCgojIyBEYXRhc2V0IE92ZXJ2aWV3CgotLS0KCiMjIyBOdW1iZXIgb2YgUm93cyBhbmQgQ29sdW1ucwpgYGB7cn0KIyBUb3RhbCBudW1iZXIgb2Ygcm93cwpudW1fcm93cyA8LSBkaW0oZGYpWzFdCgojIFRvdGFsIG51bWJlciBvZiBjb2x1bW5zCm51bV9jb2x1bW5zIDwtIGRpbShkZilbMl0KCiMgUHJpbnQgdGhlIHJlc3VsdHMKY2F0KCJOdW1iZXIgb2Ygcm93czoiLCBudW1fcm93cywgIlxuIikKY2F0KCJOdW1iZXIgb2YgY29sdW1uczoiLCBudW1fY29sdW1ucywgIlxuIikKYGBgCgojIyMgQ2hlY2tpbmcgQ29sdW1uIE5hbWVzCmBgYHtyfQojIEdldCBjb2x1bW4gbmFtZXMKYXMuZGF0YS5mcmFtZShjb2xuYW1lcyhkZikpCmBgYAoKIyMjIENoZWNraW5nIGZvciBOQSB2YWx1ZXMgYW5kIEhhbmRsaW5nIE1pc3NpbmcgRGF0YQpgYGB7cn0KYXMuZGF0YS5mcmFtZShjb2xTdW1zKGlzLm5hKGRmKSkpCmBgYAoKIyMjIElkZW50aWZ5aW5nIGFuZCBSZW1vdmluZyBEdXBsaWNhdGVzCmBgYHtyfQojIEZpbmQgZHVwbGljYXRlIHJvd3MKZGZbZHVwbGljYXRlZChkZiksIF0KYGBgCgojIyMgRGlzcGxheWluZyB0aGUgRGF0YSBTdHJ1Y3R1cmUKYGBge3J9CiMgQ2hlY2sgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZGF0YXNldApzdHIoZGYpCmBgYAoKIyMjIEdlbmVyYXRpbmcgU3VtbWFyeSBvZiBEYXRhCmBgYHtyfQojIEdldCBhIHN0YXRpc3RpY2FsIHN1bW1hcnkgb2YgbnVtZXJpY2FsIGNvbHVtbnMKc3VtbWFyeShkZikKYGBgCgojIDMuIEV4cGxhdG9yeSBEYXRhIEFuYWx5c2lzCgotLS0KCiMjIEhpc3RvZ3JhbSBBbmFseXNpcwoKVGhpcyBzZWN0aW9uIHByZXNlbnRzIHRoZSBoaXN0b2dyYW1zIGZvciB0aGUgZm9sbG93aW5nIHNpeCBrZXkgdmFyaWFibGVzIGZyb20gdGhlIGRhdGFzZXQ6CgotICoqRmlyc3QgT3JkZXIgUHJvZml0KioKLSAqKlN1YnNlcXVlbnQgT3JkZXIgUHJvZml0KioKLSAqKlN1YnNlcXVlbnQgT3JkZXJzIENvdW50KioKLSAqKlRvdGFsIFZhbHVlIG9mIEFsbCBQcm9tb3Rpb25zKioKLSAqKkFnZSoqCi0gKipUb3RhbCBQcm9maXQqKgoKVGhlc2UgaGlzdG9ncmFtcyBwcm92aWRlIGluc2lnaHRzIGludG8gdGhlIGRpc3RyaWJ1dGlvbnMgb2YgdGhlIGRhdGEsIGhlbHBpbmcgdXMgaWRlbnRpZnkgc2tld25lc3MsIHRoZSBwcmVzZW5jZSBvZiBvdXRsaWVycywgYW5kIHBvdGVudGlhbCBkYXRhIHRyYW5zZm9ybWF0aW9ucy4KCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9MTB9CiMgU2VsZWN0IHNwZWNpZmljIGNvbHVtbnMKc2VsZWN0ZWRfY29scyA8LSBjKAogICJGaXJzdF9PcmRlcl9Qcm9maXQiLCAiU3Vic2VxdWVudF9PcmRlcl9Qcm9maXQiLCAiU3Vic2VxdWVudF9PcmRlcnNfQ291bnQiLAogICJUb3RhbF92YWx1ZV9vZl9hbGxfcHJvbW90aW9ucyIsICJBZ2UiLCAiVG90YWxfUHJvZml0IgopCgojIEF1dG9tYXRpY2FsbHkgZXh0cmFjdCB0aGUgcmVxdWlyZWQgbnVtYmVyIG9mIGNvbG9ycwpjb2xvcnMgPC0gZ2V0X3Jvc2VfcGluZV9jb2xvcnMobGVuZ3RoKHNlbGVjdGVkX2NvbHMpKSAKCiMgUHJlcGFyZSB0aGUgZGF0YSBsaXN0CmRhdGFfbGlzdCA8LSBsYXBwbHkoc2VsZWN0ZWRfY29scywgZnVuY3Rpb24oY29sKSBkZltbY29sXV0pCm5hbWVzKGRhdGFfbGlzdCkgPC0gc2VsZWN0ZWRfY29scwoKIyBHZW5lcmF0ZSBIaXN0b2dyYW1zIER5bmFtaWNhbGx5Cmhpc3RvZ3JhbXMgPC0gbGFwcGx5KHNlcV9hbG9uZyhkYXRhX2xpc3QpLCBmdW5jdGlvbihpKSB7CiAgZm9ybWF0dGVkX3RpdGxlIDwtIHRvb2xzOjp0b1RpdGxlQ2FzZShnc3ViKCJfIiwgIiAiLCBuYW1lcyhkYXRhX2xpc3QpW2ldKSkgICMgRm9ybWF0IGNvbHVtbiBuYW1lCiAgCiAgZ2dwbG90KGRhdGEuZnJhbWUodmFsdWUgPSBkYXRhX2xpc3RbW2ldXSksIGFlcyh4ID0gdmFsdWUpKSArCiAgICBnZW9tX2hpc3RvZ3JhbV9kZWZhdWx0KGRhdGFfbGVuZ3RoID0gbGVuZ3RoKGRhdGFfbGlzdFtbaV1dKSwgZmlsbF9jb2xvciA9IGNvbG9yc1tpXSkgKyAgIyBEeW5hbWljIGJpbnMgJiBjb2xvcgogICAgbGFicygKICAgICAgdGl0bGUgPSBwYXN0ZSgiIiwgZm9ybWF0dGVkX3RpdGxlKSwgICMgVXNlIGZvcm1hdHRlZCB0aXRsZQogICAgICB4ID0gIiIsCiAgICAgIHkgPSAiRnJlcXVlbmN5IgogICAgKSArIAogICAgdGhlbWVfcm9zZV9waW5lKCkKfSkKCiMgQ29tYmluZSBBbGwgSGlzdG9ncmFtcyBpbnRvIGEgR3JpZCBMYXlvdXQKZG8uY2FsbChncmlkLmFycmFuZ2UsIGMoaGlzdG9ncmFtcywgbmNvbCA9IDMpKQpgYGAKIyMjIE9ic2VydmF0aW9ucyBhbmQgUmVjb21tZW5kYXRpb25zCgojIyMjICoqMS4gRmlyc3QgT3JkZXIgUHJvZml0KioKLSAqKk9ic2VydmF0aW9uKio6CiAgLSBIaWdobHkgcmlnaHQtc2tld2VkIGRpc3RyaWJ1dGlvbiB3aXRoIG1vc3QgdmFsdWVzIG5lYXIgemVyby4KICAtIEEgbG9uZyB0YWlsIHN1Z2dlc3RzIGhpZ2gtcHJvZml0IG91dGxpZXJzLgotICoqUmVjb21tZW5kYXRpb24qKjoKICAtIEFwcGx5IGEgbG9nYXJpdGhtaWMgdHJhbnNmb3JtYXRpb24gdG8gcmVkdWNlIHNrZXduZXNzIGZvciBhbmFseXNpcy4KICAtIEludmVzdGlnYXRlIG91dGxpZXJzIHRvIGRldGVybWluZSB0aGVpciB2YWxpZGl0eSBvciBjb25zaWRlciBjYXBwaW5nIHRoZW0uCgotLS0KCiMjIyMgKioyLiBTdWJzZXF1ZW50IE9yZGVyIFByb2ZpdCoqCi0gKipPYnNlcnZhdGlvbioqOgogIC0gUmlnaHQtc2tld2VkIHdpdGggbW9zdCB2YWx1ZXMgbmVhciB6ZXJvIGFuZCBhIGZldyB2ZXJ5IGhpZ2ggcHJvZml0cy4KICAtIFRoZSBkaXN0cmlidXRpb24gaXMgc2ltaWxhciB0byB0aGUgRmlyc3QgT3JkZXIgUHJvZml0LgotICoqUmVjb21tZW5kYXRpb24qKjoKICAtIFVzZSBzaW1pbGFyIHRyZWF0bWVudCBhcyBGaXJzdCBPcmRlciBQcm9maXQgKGUuZy4sIGxvZ2FyaXRobWljIHRyYW5zZm9ybWF0aW9uIG9yIG91dGxpZXIgY2FwcGluZykuCiAgLSBFeHBsb3JlIGN1c3RvbWVyIHNlZ21lbnRzIHRoYXQgZ2VuZXJhdGUgaGlnaCBzdWJzZXF1ZW50IHByb2ZpdHMuCgotLS0KCiMjIyMgKiozLiBTdWJzZXF1ZW50IE9yZGVycyBDb3VudCoqCi0gKipPYnNlcnZhdGlvbioqOgogIC0gTWFqb3JpdHkgb2YgY3VzdG9tZXJzIHBsYWNlZCB2ZXJ5IGZldyBzdWJzZXF1ZW50IG9yZGVycyAoKiriiaQgNSoqKS4KICAtIEEgc2hhcnAgZGVjbGluZSBpbiBjdXN0b21lciBjb3VudCBhcyB0aGUgbnVtYmVyIG9mIG9yZGVycyBpbmNyZWFzZXMuCi0gKipSZWNvbW1lbmRhdGlvbioqOgogIC0gR3JvdXAgaGlnaGVyIG9yZGVyIGNvdW50cyBpbnRvIGJpbnMgZm9yIGJldHRlciB2aXN1YWwgaW50ZXJwcmV0YWJpbGl0eS4KICAtIEludmVzdGlnYXRlIGZhY3RvcnMgaW5mbHVlbmNpbmcgY3VzdG9tZXJzIHdpdGggaGlnaGVyIG9yZGVyIGNvdW50cyAoZS5nLiwgYWdlLCBwcm9tb3Rpb25zKS4KCi0tLQoKIyMjIyAqKjQuIFRvdGFsIFZhbHVlIG9mIEFsbCBQcm9tb3Rpb25zKioKLSAqKk9ic2VydmF0aW9uKio6CiAgLSBIaWdobHkgc2tld2VkIHdpdGggbW9zdCBjdXN0b21lcnMgcmVjZWl2aW5nIHByb21vdGlvbnMgb2YgbG93IHZhbHVlLgogIC0gRmV3IGN1c3RvbWVycyByZWNlaXZlZCBzaWduaWZpY2FudGx5IGhpZ2gtdmFsdWUgcHJvbW90aW9ucywgY3JlYXRpbmcgYSBsb25nIHRhaWwuCi0gKipSZWNvbW1lbmRhdGlvbioqOgogIC0gSW52ZXN0aWdhdGUgd2hldGhlciBoaWdoIHByb21vdGlvbiB2YWx1ZXMgY29ycmVsYXRlIHdpdGggaGlnaCBwcm9maXQgb3Igb3JkZXIgY291bnRzLgogIC0gQ29uc2lkZXIgZ3JvdXBpbmcgcHJvbW90aW9uIHZhbHVlcyBpbnRvIGJpbnMgZm9yIGJldHRlciBpbnRlcnByZXRhYmlsaXR5LgoKLS0tCgojIyMjICoqNS4gQWdlKioKLSAqKk9ic2VydmF0aW9uKio6CiAgLSBBcHByb3hpbWF0ZWx5IGJlbGwtc2hhcGVkLCB3aXRoIG1vc3QgY3VzdG9tZXJzIGZhbGxpbmcgYmV0d2VlbiAyMOKAkzUwIHllYXJzIG9mIGFnZS4KICAtIEZld2VyIGN1c3RvbWVycyBhcmUgeW91bmdlciB0aGFuIDIwIG9yIG9sZGVyIHRoYW4gNTAuCi0gKipSZWNvbW1lbmRhdGlvbioqOgogIC0gTm8gaW1tZWRpYXRlIGFjdGlvbiByZXF1aXJlZDsgdGhlIGRpc3RyaWJ1dGlvbiBpcyBiYWxhbmNlZCBhbmQgY2FuIGJlIHVzZWQgYXMtaXMgZm9yIG1vZGVsaW5nLgoKLS0tCgojIyMjICoqNi4gVG90YWwgUHJvZml0KioKLSAqKk9ic2VydmF0aW9uKio6CiAgLSBTaW1pbGFyIHRvIHByb2ZpdCB2YXJpYWJsZXMsIHRoZSBkaXN0cmlidXRpb24gaXMgcmlnaHQtc2tld2VkIHdpdGggbW9zdCB2YWx1ZXMgY29uY2VudHJhdGVkIG5lYXIgemVyby4KICAtIEhpZ2gtcHJvZml0IG91dGxpZXJzIGFyZSBwcmVzZW50LgotICoqUmVjb21tZW5kYXRpb24qKjoKICAtIEludmVzdGlnYXRlIGhpZ2gtcHJvZml0IGN1c3RvbWVycyBhbmQgdGhlaXIgY2hhcmFjdGVyaXN0aWNzLgogIC0gQ29uc2lkZXIgdHJhbnNmb3JtYXRpb25zIG9yIG91dGxpZXIgaGFuZGxpbmcgZm9yIG1vZGVsaW5nIHB1cnBvc2VzLgogIApUaGlzIGFuYWx5c2lzIGhlbHBzIGlkZW50aWZ5IHBvdGVudGlhbCBwcmVwcm9jZXNzaW5nIHN0ZXBzIGFuZCBndWlkZXMgZXhwbG9yYXRvcnkgYW5hbHlzaXMgdG8gYmV0dGVyIHVuZGVyc3RhbmQgY3VzdG9tZXIgYmVoYXZpb3IuCgotLS0KCiMjIERlbnNpdHkgUGxvdCBBbmFseXNpcwoKVGhpcyBzZWN0aW9uIHByb3ZpZGVzIG9ic2VydmF0aW9ucyBhbmQgcmVjb21tZW5kYXRpb25zIGJhc2VkIG9uIHRoZSBkZW5zaXR5IHBsb3RzIG9mIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzOgoKLSAqKkZpcnN0IE9yZGVyIFByb2ZpdCoqCi0gKipTdWJzZXF1ZW50IE9yZGVyIFByb2ZpdCoqCi0gKipTdWJzZXF1ZW50IE9yZGVycyBDb3VudCoqCi0gKipUb3RhbCBWYWx1ZSBvZiBBbGwgUHJvbW90aW9ucyoqCi0gKipBZ2UqKgotICoqVG90YWwgUHJvZml0KioKCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9MTB9CiMgR2VuZXJhdGUgS0RFIHBsb3RzIGR5bmFtaWNhbGx5CmtkZSA8LSBsYXBwbHkoc2VxX2Fsb25nKGRhdGFfbGlzdCksIGZ1bmN0aW9uKGkpIHsKICBmb3JtYXR0ZWRfdGl0bGUgPC0gdG9vbHM6OnRvVGl0bGVDYXNlKGdzdWIoIl8iLCAiICIsIG5hbWVzKGRhdGFfbGlzdClbaV0pKQogIAogIGdncGxvdChkYXRhLmZyYW1lKHZhbHVlID0gZGF0YV9saXN0W1tpXV0pLCBhZXMoeCA9IHZhbHVlKSkgKwogICAgZ2VvbV9kZW5zaXR5X2RlZmF1bHQoZmlsbF9jb2xvciA9IGNvbG9yc1tpXSkgKyAgIyBEeW5hbWljIGNvbG9yIGFzc2lnbm1lbnQKICAgIGxhYnMoCiAgICAgIHRpdGxlID0gcGFzdGUoIiIsIGZvcm1hdHRlZF90aXRsZSksCiAgICAgIHggPSAiIiwKICAgICAgeSA9ICJEZW5zaXR5IgogICAgKSArCiAgICB0aGVtZV9yb3NlX3BpbmUoKQp9KQoKIyBDb21iaW5lIGFsbCBwbG90cyBpbnRvIGEgZ3JpZCBsYXlvdXQKZG8uY2FsbChncmlkLmFycmFuZ2UsIGMoa2RlLCBuY29sID0gMykpCmBgYAojIyMgT2JzZXJ2YXRpb25zIGFuZCBSZWNvbW1lbmRhdGlvbnMKCiMjIyMgKioxLiBGaXJzdCBPcmRlciBQcm9maXQqKgotICoqT2JzZXJ2YXRpb24qKjoKICAtIFJpZ2h0LXNrZXdlZCBkaXN0cmlidXRpb24gd2l0aCBhIHNoYXJwIHBlYWsgYXQgbG93IHByb2ZpdCB2YWx1ZXMuCiAgLSBMb25nIHRhaWwgZHVlIHRvIGhpZ2gtcHJvZml0IGN1c3RvbWVycy4KLSAqKlJlY29tbWVuZGF0aW9uKio6CiAgLSBBcHBseSBhIGxvZ2FyaXRobWljIHRyYW5zZm9ybWF0aW9uIGZvciBza2V3bmVzcyBjb3JyZWN0aW9uLgogIC0gRXhhbWluZSBvdXRsaWVycyBmb3IgdmFsaWRpdHkgb3IgYXBwbHkgY2FwcGluZyB0byByZWR1Y2UgaW5mbHVlbmNlLgoKLS0tCgojIyMjICoqMi4gU3Vic2VxdWVudCBPcmRlciBQcm9maXQqKgotICoqT2JzZXJ2YXRpb24qKjoKICAtIFNoYXJwIHBlYWsgYXQgbG93IHZhbHVlcywgd2l0aCBhIHNpbWlsYXIgcmlnaHQtc2tld2VkIHRyZW5kIGFzIEZpcnN0IE9yZGVyIFByb2ZpdC4KICAtIExvbmcgdGFpbCBleHRlbmRpbmcgdG93YXJkcyB2ZXJ5IGhpZ2ggdmFsdWVzLgotICoqUmVjb21tZW5kYXRpb24qKjoKICAtIENvbnNpZGVyIGhhbmRsaW5nIG91dGxpZXJzIHRocm91Z2ggY2FwcGluZyBvciBXaW5zb3JpemF0aW9uLgogIC0gSW52ZXN0aWdhdGUgY3VzdG9tZXIgc2VnbWVudHMgY29udHJpYnV0aW5nIHRvIGhpZ2ggc3Vic2VxdWVudCBwcm9maXRzLgoKLS0tCgojIyMjICoqMy4gU3Vic2VxdWVudCBPcmRlcnMgQ291bnQqKgotICoqT2JzZXJ2YXRpb24qKjoKICAtIENsZWFyIHBlYWsgYXQgMeKAkzIgb3JkZXJzLCBpbmRpY2F0aW5nIG1vc3QgY3VzdG9tZXJzIG1hZGUgZmV3IHJlcGVhdCBwdXJjaGFzZXMuCiAgLSBEaXN0cmlidXRpb24gZmxhdHRlbnMgc2lnbmlmaWNhbnRseSBmb3IgaGlnaGVyIG9yZGVyIGNvdW50cy4KLSAqKlJlY29tbWVuZGF0aW9uKio6CiAgLSBHcm91cCBoaWdoZXIgY291bnRzIGludG8gYmlucyBmb3IgY2xlYXJlciBpbnNpZ2h0cy4KICAtIEV4cGxvcmUgZmFjdG9ycyAoZS5nLiwgZGVtb2dyYXBoaWNzLCBwcm9tb3Rpb25zKSBpbmZsdWVuY2luZyBjdXN0b21lcnMgd2l0aCBtYW55IG9yZGVycy4KCi0tLQoKIyMjIyAqKjQuIFRvdGFsIFZhbHVlIG9mIEFsbCBQcm9tb3Rpb25zKioKLSAqKk9ic2VydmF0aW9uKio6CiAgLSBIaWdobHkgc2tld2VkLCB3aXRoIG1vc3QgdmFsdWVzIGNvbmNlbnRyYXRlZCBuZWFyIHplcm8uCiAgLSBMb25nIHRhaWwgc3VnZ2VzdHMgYSBmZXcgY3VzdG9tZXJzIHJlY2VpdmVkIHNpZ25pZmljYW50bHkgaGlnaCBwcm9tb3Rpb24gdmFsdWVzLgotICoqUmVjb21tZW5kYXRpb24qKjoKICAtIEFzc2VzcyB3aGV0aGVyIGhpZ2ggcHJvbW90aW9uIHZhbHVlcyB0cmFuc2xhdGUgdG8gaGlnaGVyIHByb2ZpdHMgb3Igb3JkZXIgY291bnRzLgogIC0gQ29uc2lkZXIgc2VnbWVudGluZyBjdXN0b21lcnMgYmFzZWQgb24gcHJvbW90aW9uIGxldmVscyBmb3IgZnVydGhlciBhbmFseXNpcy4KCi0tLQoKIyMjIyAqKjUuIEFnZSoqCi0gKipPYnNlcnZhdGlvbioqOgogIC0gRGlzdHJpYnV0aW9uIGlzIGFwcHJveGltYXRlbHkgYmVsbC1zaGFwZWQsIGNlbnRlcmVkIGFyb3VuZCAzMOKAkzQwIHllYXJzLgogIC0gU2tld25lc3MgaXMgbWluaW1hbCwgd2l0aCBiYWxhbmNlZCBkYXRhIGZvciBtb3N0IGFnZSByYW5nZXMuCi0gKipSZWNvbW1lbmRhdGlvbioqOgogIC0gTm8gdHJhbnNmb3JtYXRpb24gbmVlZGVkLiBUaGUgdmFyaWFibGUgY2FuIGJlIHVzZWQgYXMtaXMgaW4gYW5hbHlzaXMuCgotLS0KCiMjIyMgKio2LiBUb3RhbCBQcm9maXQqKgotICoqT2JzZXJ2YXRpb24qKjoKICAtIFNpbWlsYXIgcmlnaHQtc2tld2VkIHRyZW5kIGFzIG90aGVyIHByb2ZpdCB2YXJpYWJsZXMsIHdpdGggYSBwZWFrIGF0IGxvdyB2YWx1ZXMuCiAgLSBMb25nIHRhaWwgc2hvd3MgdGhlIHByZXNlbmNlIG9mIGEgZmV3IGhpZ2gtcHJvZml0IGN1c3RvbWVycy4KLSAqKlJlY29tbWVuZGF0aW9uKio6CiAgLSBDb25zaWRlciB0cmFuc2Zvcm1hdGlvbnMgdG8gYWRkcmVzcyBza2V3bmVzcyBhbmQgcmVkdWNlIG91dGxpZXIgZWZmZWN0cy4KICAtIEludmVzdGlnYXRlIGhpZ2gtcHJvZml0IGN1c3RvbWVycyB0byB1bmRlcnN0YW5kIHRoZWlyIGNoYXJhY3RlcmlzdGljcy4KClRoaXMgZGVuc2l0eSBwbG90IGFuYWx5c2lzIGhpZ2hsaWdodHMga2V5IHRyZW5kcyBhbmQgcHJvdmlkZXMgYWN0aW9uYWJsZSByZWNvbW1lbmRhdGlvbnMgZm9yIHByZXByb2Nlc3NpbmcgYW5kIGZ1cnRoZXIgZXhwbG9yYXRpb24uCgotLS0KCiMjIEdlbmRlciBEaXN0cmlidXRpb24gQW5hbHlzaXMKClRoaXMgc2VjdGlvbiB2aXN1YWxpemVzIHRoZSBkaXN0cmlidXRpb24gb2YgZ2VuZGVyIGFjcm9zcyB0aGUgZGF0YXNldCB1c2luZyBhIGJhciBwbG90IGFuZCBhIHBpZSBjaGFydC4KCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9OH0KZ2VuZGVyX2NvdW50cyA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGRmJEdlbmRlcikpCm5hbWVzKGdlbmRlcl9jb3VudHMpIDwtIGMoIkdlbmRlciIsICJDb3VudCIpCgojIENyZWF0ZSBDb3VudCBQbG90IHVzaW5nIGdlb21fY291bnRfZGVmYXVsdApjb3VudF9wbG90IDwtIGdncGxvdChnZW5kZXJfY291bnRzLCBhZXMoeCA9IEdlbmRlciwgeSA9IENvdW50LCBmaWxsID0gR2VuZGVyKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfcm9zZV9waW5lKCkgKyAgIyBBcHBseSB0aGUgY3VzdG9tIHRoZW1lCiAgc2NhbGVfZmlsbF9yb3NlX3BpbmUoKSArICAjIEFwcGx5IHRoZSBmaWxsIGNvbG9yIHNjYWxlCiAgbGFicyh0aXRsZSA9ICJHZW5kZXIgRGlzdHJpYnV0aW9uIFBsb3QiLCB4ID0gIkdlbmRlciIsIHkgPSAiQ291bnQiKSArICAjIFRpdGxlIGFuZCBheGlzIGxhYmVscwogIHRoZW1lKAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTgsIGZhY2UgPSAiYm9sZCIsIGNvbG9yID0gIndoaXRlIiksICAjIFRpdGxlIGZvbnQgc2l6ZSBhbmQgY29sb3IKICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQsIGNvbG9yID0gIndoaXRlIiksICAjIFgtYXhpcyBsYWJlbCBmb250IHNpemUgYW5kIGNvbG9yCiAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0LCBjb2xvciA9ICJ3aGl0ZSIpLCAgIyBZLWF4aXMgbGFiZWwgZm9udCBzaXplIGFuZCBjb2xvcgogICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgY29sb3IgPSAid2hpdGUiKSwgICMgQXhpcyB0aWNrIGxhYmVscyBmb250IHNpemUgYW5kIGNvbG9yCiAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLAogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMywgY29sb3IgPSAid2hpdGUiKSwgICMgTGVnZW5kIHRpdGxlIGZvbnQgc2l6ZSBhbmQgY29sb3IKICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSwgY29sb3IgPSAid2hpdGUiKSwgICMgTGVnZW5kIHRleHQgZm9udCBzaXplIGFuZCBjb2xvcgogICAgbGVnZW5kLmtleS5zaXplID0gdW5pdCgxLCAiY20iKSAgIyBBZGp1c3QgdGhlIHNpemUgb2YgbGVnZW5kIGtleXMgKHRoZSBjb2xvciBib3hlcykKICApCgojIEdlbmVyYXRlIHRoZSBQaWUgQ2hhcnQKcGllX2NoYXJ0IDwtIGdlb21fcGllX2RlZmF1bHQoCiAgZGF0YSA9IGdlbmRlcl9jb3VudHMsCiAgY2F0ZWdvcnlfY29sID0gIkdlbmRlciIsCiAgdmFsdWVfY29sID0gIkNvdW50IgopICsgCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCikKCiMgQ29tYmluZSB0aGUgcGxvdHMgdXNpbmcgZ3JpZC5hcnJhbmdlCmdyaWQuYXJyYW5nZShjb3VudF9wbG90LCBwaWVfY2hhcnQsIG5jb2wgPSAyKQpgYGAKIyMjIE9ic2VydmF0aW9ucwoKIyMjIyAqKjEuIEJhciBQbG90KioKLSAqKk9ic2VydmF0aW9uKio6CiAgLSBUaGUgbWFqb3JpdHkgb2YgY3VzdG9tZXJzIGFyZSAqKk1hbGUqKiwgbWFraW5nIHVwIHRoZSBsYXJnZXN0IGdyb3VwLgogIC0gQSBzbWFsbGVyIHByb3BvcnRpb24gb2YgY3VzdG9tZXJzIGFyZSAqKkZlbWFsZSoqLgogIC0gQSBub3RhYmxlIHBlcmNlbnRhZ2Ugb2YgY3VzdG9tZXJzIGZhbGwgaW50byB0aGUgKipOb3QgRGVmaW5lZCoqIGNhdGVnb3J5LCB3aGljaCBtaWdodCBpbmRpY2F0ZSBtaXNzaW5nIG9yIHVuc3BlY2lmaWVkIGRhdGEuCgojIyMjICoqMi4gUGllIENoYXJ0KioKLSAqKk9ic2VydmF0aW9uKio6CiAgLSAqKk1hbGUqKiBjdXN0b21lcnMgY29uc3RpdHV0ZSBhcHByb3hpbWF0ZWx5ICoqNjMuNSUqKiBvZiB0aGUgZGF0YXNldC4KICAtICoqRmVtYWxlKiogY3VzdG9tZXJzIGFjY291bnQgZm9yICoqMjMuNSUqKi4KICAtIFRoZSAqKk5vdCBEZWZpbmVkKiogY2F0ZWdvcnkgcmVwcmVzZW50cyAqKjEzJSoqLgoKIyMjIyAqKlJlY29tbWVuZGF0aW9ucyoqCi0gSW52ZXN0aWdhdGUgdGhlICoqTm90IERlZmluZWQqKiBjYXRlZ29yeSB0byBkZXRlcm1pbmUgd2hldGhlciB0aGlzIGRhdGEgY2FuIGJlIGNsYXJpZmllZCBvciBleGNsdWRlZC4KLSBVc2UgdGhpcyBkaXN0cmlidXRpb24gdG8gc2VnbWVudCBhbmFseXNpcyBvciBtYXJrZXRpbmcgc3RyYXRlZ2llcyBiYXNlZCBvbiBnZW5kZXIuCgotLS0KCiMjIE1hcmtldGluZyBDaGFubmVsIFR5cGUgRGlzdHJpYnV0aW9uCgpUaGlzIHNlY3Rpb24gdmlzdWFsaXplcyB0aGUgZGlzdHJpYnV0aW9uIG9mIG1hcmtldGluZyBjaGFubmVscywgcHJvdmlkaW5nIGluc2lnaHRzIGludG8gd2hpY2ggY2hhbm5lbHMgYXJlIG1vc3QgZWZmZWN0aXZlIGluIGFjcXVpcmluZyBjdXN0b21lcnMuCgpgYGB7ciwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTh9Cm1hcmtldGluZ19jaGFubmVsX3R5cGVfY291bnQgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShkZiRNYXJrZXRpbmdfQ2hhbm5lbF9UeXBlKSkKbmFtZXMobWFya2V0aW5nX2NoYW5uZWxfdHlwZV9jb3VudCkgPC0gYygiTWFya2V0aW5nX0NoYW5uZWxfVHlwZSIsICJDb3VudCIpCgojIENyZWF0ZSBDb3VudCBQbG90IHVzaW5nIGdlb21fY291bnRfZGVmYXVsdApjb3VudF9wbG90IDwtIGdncGxvdChtYXJrZXRpbmdfY2hhbm5lbF90eXBlX2NvdW50LCBhZXMoeCA9IE1hcmtldGluZ19DaGFubmVsX1R5cGUsIHkgPSBDb3VudCwgZmlsbCA9IE1hcmtldGluZ19DaGFubmVsX1R5cGUpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZV9yb3NlX3BpbmUoKSArICAjIEFwcGx5IHRoZSBjdXN0b20gdGhlbWUKICBzY2FsZV9maWxsX3Jvc2VfcGluZSgpICsgICMgQXBwbHkgdGhlIGZpbGwgY29sb3Igc2NhbGUKICBsYWJzKHRpdGxlID0gIk1hcmtldGluZyBDaGFubmVsIFR5cGUgRGlzdHJpYnV0aW9uIiwgeCA9ICIiLCB5ID0gIkNvdW50IikgKyAgIyBUaXRsZSBhbmQgYXhpcyBsYWJlbHMKICB0aGVtZSgKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE4LCBmYWNlID0gImJvbGQiLCBjb2xvciA9ICJ3aGl0ZSIpLCAgIyBUaXRsZSBmb250IHNpemUgYW5kIGNvbG9yCiAgICAjYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCwgY29sb3IgPSAid2hpdGUiKSwgICMgWC1heGlzIGxhYmVsIGZvbnQgc2l6ZSBhbmQgY29sb3IKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCAgIyBIaWRlIHgtYXhpcyB0ZXh0CiAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0LCBjb2xvciA9ICJ3aGl0ZSIpLCAgIyBZLWF4aXMgbGFiZWwgZm9udCBzaXplIGFuZCBjb2xvcgogICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgY29sb3IgPSAid2hpdGUiLCBhbmdsZSA9IDkwKSwgICMgQXhpcyB0aWNrIGxhYmVscyBmb250IHNpemUgYW5kIGNvbG9yCiAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLAogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMywgY29sb3IgPSAid2hpdGUiKSwgICMgTGVnZW5kIHRpdGxlIGZvbnQgc2l6ZSBhbmQgY29sb3IKICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSwgY29sb3IgPSAid2hpdGUiKSwgICMgTGVnZW5kIHRleHQgZm9udCBzaXplIGFuZCBjb2xvcgogICAgbGVnZW5kLmtleS5zaXplID0gdW5pdCgxLCAiY20iKSAgIyBBZGp1c3QgdGhlIHNpemUgb2YgbGVnZW5kIGtleXMgKHRoZSBjb2xvciBib3hlcykKICApCgojIEdlbmVyYXRlIHRoZSBQaWUgQ2hhcnQKcGllX2NoYXJ0IDwtIGdlb21fcGllX2RlZmF1bHQoCiAgZGF0YSA9IG1hcmtldGluZ19jaGFubmVsX3R5cGVfY291bnQsCiAgY2F0ZWdvcnlfY29sID0gIk1hcmtldGluZ19DaGFubmVsX1R5cGUiLAogIHZhbHVlX2NvbCA9ICJDb3VudCIKKSArIAogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAopCgojIENvbWJpbmUgdGhlIHBsb3RzIHVzaW5nIGdyaWQuYXJyYW5nZQpncmlkLmFycmFuZ2UoY291bnRfcGxvdCwgcGllX2NoYXJ0LCBuY29sID0gMikKYGBgCiMjIyBPYnNlcnZhdGlvbnMKCiMjIyMgKioxLiBCYXIgUGxvdCoqCi0gKipPYnNlcnZhdGlvbioqOgogIC0gVGhlICoqRGlyZWN0KiogY2hhbm5lbCBjb250cmlidXRlcyB0aGUgbGFyZ2VzdCBzaGFyZSwgd2l0aCBhcHByb3hpbWF0ZWx5ICoqMzUuOCUqKiBvZiBjdXN0b21lcnMuCiAgLSAqKk9yZ2FuaWMgU2VhcmNoKiogYW5kICoqUGFpZCBTZWFyY2gqKiBhcmUgdGhlIHNlY29uZCBhbmQgdGhpcmQgbW9zdCBjb21tb24gY2hhbm5lbHMsIGF0ICoqMjYuNyUqKiBhbmQgKioyMC44JSoqLCByZXNwZWN0aXZlbHkuCiAgLSAqKkFmZmlsaWF0ZXMqKiByZXByZXNlbnQgKioxMi4zJSoqLCBhbmQgKipQYWlkIFNvY2lhbCoqIGNvbnRyaWJ1dGVzIHRoZSBzbWFsbGVzdCBzaGFyZSBhdCAqKjQuNSUqKi4KCiMjIyMgKioyLiBQaWUgQ2hhcnQqKgotICoqT2JzZXJ2YXRpb24qKjoKICAtIFRoZSBwcm9wb3J0aW9uYWwgdmlldyBjb25maXJtcyB0aGF0ICoqRGlyZWN0KiosICoqT3JnYW5pYyBTZWFyY2gqKiwgYW5kICoqUGFpZCBTZWFyY2gqKiBhcmUgdGhlIGRvbWluYW50IGNoYW5uZWxzLgogIC0gKipQYWlkIFNvY2lhbCoqLCBkZXNwaXRlIGJlaW5nIGEgbWlub3IgY2hhbm5lbCwgbWlnaHQgdGFyZ2V0IG5pY2hlIGN1c3RvbWVyIHNlZ21lbnRzLgoKIyMjIyAqKlJlY29tbWVuZGF0aW9ucyoqCi0gRm9jdXMgbWFya2V0aW5nIGVmZm9ydHMgb24gKipEaXJlY3QqKiBhbmQgKipPcmdhbmljIFNlYXJjaCoqIGNoYW5uZWxzLCBhcyB0aGV5IGhhdmUgYSBzaWduaWZpY2FudCBpbXBhY3QuCi0gRXZhbHVhdGUgdGhlIHBlcmZvcm1hbmNlIG9mICoqUGFpZCBTb2NpYWwqKiB0byBkZXRlcm1pbmUgd2hldGhlciBpdCBpcyBjb3N0LWVmZmVjdGl2ZSBvciByZXF1aXJlcyBhZGp1c3RtZW50cy4KLSBFeHBsb3JlIG9wcG9ydHVuaXRpZXMgdG8gaW1wcm92ZSBjdXN0b21lciBhY3F1aXNpdGlvbiB0aHJvdWdoICoqQWZmaWxpYXRlcyoqIGJ5IHJlZmluaW5nIHRoZSBzdHJhdGVneS4KCi0tLQoKIyMgQ29udGFjdCBBbGxvd2VkIERpc3RyaWJ1dGlvbgoKVGhpcyBzZWN0aW9uIGV4YW1pbmVzIHRoZSBkaXN0cmlidXRpb24gb2YgY3VzdG9tZXIgY29uc2VudCBmb3IgYmVpbmcgY29udGFjdGVkLCByZXByZXNlbnRlZCBieSB0d28gdmlzdWFsaXphdGlvbnM6IGEgYmFyIHBsb3QgYW5kIGEgcGllIGNoYXJ0LgoKYGBge3IsIGZpZy53aWR0aD0xNiwgZmlnLmhlaWdodD04fQpjb250YWN0X2FsbG93ZWRfY291bnQgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShkZiRDb250YWN0X0FsbG93ZWQpKQpuYW1lcyhjb250YWN0X2FsbG93ZWRfY291bnQpIDwtIGMoIkNvbnRhY3RfQWxsb3dlZCIsICJDb3VudCIpCgojIENyZWF0ZSBDb3VudCBQbG90IHVzaW5nIGdlb21fY291bnRfZGVmYXVsdApjb3VudF9wbG90IDwtIGdncGxvdChjb250YWN0X2FsbG93ZWRfY291bnQsIGFlcyh4ID0gQ29udGFjdF9BbGxvd2VkLCB5ID0gQ291bnQsIGZpbGwgPSBDb250YWN0X0FsbG93ZWQpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZV9yb3NlX3BpbmUoKSArICAjIEFwcGx5IHRoZSBjdXN0b20gdGhlbWUKICBzY2FsZV9maWxsX3Jvc2VfcGluZSgpICsgICMgQXBwbHkgdGhlIGZpbGwgY29sb3Igc2NhbGUKICBsYWJzKHRpdGxlID0gIkNvbnRhY3QgQWxsb3dlZCBEaXN0cmlidXRpb24iLCB4ID0gIiIsIHkgPSAiQ291bnQiKSArICAjIFRpdGxlIGFuZCBheGlzIGxhYmVscwogIHRoZW1lKAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTgsIGZhY2UgPSAiYm9sZCIsIGNvbG9yID0gIndoaXRlIiksICAjIFRpdGxlIGZvbnQgc2l6ZSBhbmQgY29sb3IKICAgICNheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0LCBjb2xvciA9ICJ3aGl0ZSIpLCAgIyBYLWF4aXMgbGFiZWwgZm9udCBzaXplIGFuZCBjb2xvcgogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksICAjIEhpZGUgeC1heGlzIHRleHQKICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQsIGNvbG9yID0gIndoaXRlIiksICAjIFktYXhpcyBsYWJlbCBmb250IHNpemUgYW5kIGNvbG9yCiAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBjb2xvciA9ICJ3aGl0ZSIsIGFuZ2xlID0gOTApLCAgIyBBeGlzIHRpY2sgbGFiZWxzIGZvbnQgc2l6ZSBhbmQgY29sb3IKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsCiAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEzLCBjb2xvciA9ICJ3aGl0ZSIpLCAgIyBMZWdlbmQgdGl0bGUgZm9udCBzaXplIGFuZCBjb2xvcgogICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDExLCBjb2xvciA9ICJ3aGl0ZSIpLCAgIyBMZWdlbmQgdGV4dCBmb250IHNpemUgYW5kIGNvbG9yCiAgICBsZWdlbmQua2V5LnNpemUgPSB1bml0KDEsICJjbSIpICAjIEFkanVzdCB0aGUgc2l6ZSBvZiBsZWdlbmQga2V5cyAodGhlIGNvbG9yIGJveGVzKQogICkKCiMgR2VuZXJhdGUgdGhlIFBpZSBDaGFydApwaWVfY2hhcnQgPC0gZ2VvbV9waWVfZGVmYXVsdCgKICBkYXRhID0gY29udGFjdF9hbGxvd2VkX2NvdW50LAogIGNhdGVnb3J5X2NvbCA9ICJDb250YWN0X0FsbG93ZWQiLAogIHZhbHVlX2NvbCA9ICJDb3VudCIKKSArIAogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAopCgojIENvbWJpbmUgdGhlIHBsb3RzIHVzaW5nIGdyaWQuYXJyYW5nZQpncmlkLmFycmFuZ2UoY291bnRfcGxvdCwgcGllX2NoYXJ0LCBuY29sID0gMikKYGBgCiMjIyBPYnNlcnZhdGlvbnMKCiMjIyMgKioxLiBCYXIgUGxvdCoqCi0gKipPYnNlcnZhdGlvbioqOgogIC0gQXBwcm94aW1hdGVseSAqKjU3LjclKiogb2YgY3VzdG9tZXJzIGhhdmUgYWxsb3dlZCBjb250YWN0LgogIC0gKio0Mi4zJSoqIG9mIGN1c3RvbWVycyBoYXZlIG9wdGVkIG91dCBvZiBiZWluZyBjb250YWN0ZWQuCgojIyMjICoqMi4gUGllIENoYXJ0KioKLSAqKk9ic2VydmF0aW9uKio6CiAgLSBUaGUgcGllIGNoYXJ0IGNvbmZpcm1zIHRoYXQgdGhlIG1ham9yaXR5IG9mIGN1c3RvbWVycyBhbGxvdyBjb250YWN0LCB0aG91Z2ggYSBzaWduaWZpY2FudCBwb3J0aW9uIHByZWZlcnMgbm90IHRvIGJlIGNvbnRhY3RlZC4KCiMjIyMgKipSZWNvbW1lbmRhdGlvbnMqKgotIEZvY3VzIG91dHJlYWNoIGFuZCBtYXJrZXRpbmcgc3RyYXRlZ2llcyBvbiB0aGUgKio1Ny43JSoqIG9mIGN1c3RvbWVycyB3aG8gYWxsb3cgY29udGFjdC4KLSBDb25zaWRlciBhbHRlcm5hdGl2ZSwgbm9uLWludHJ1c2l2ZSBtYXJrZXRpbmcgc3RyYXRlZ2llcyBmb3IgdGhlICoqNDIuMyUqKiBvZiBjdXN0b21lcnMgd2hvIGRvIG5vdCBhbGxvdyBjb250YWN0LCBzdWNoIGFzIHBlcnNvbmFsaXplZCBlbWFpbHMgb3IgdGFyZ2V0ZWQgYWRzLgoKLS0tCgojIyBUb3AgMjAgTG9jYXRpb24gQ291bnQgQW5hbHlzaXMKClRoaXMgc2VjdGlvbiBleHBsb3JlcyB0aGUgZGlzdHJpYnV0aW9uIG9mIGN1c3RvbWVyIGNvdW50cyBhY3Jvc3MgdGhlIHRvcCAyMCBsb2NhdGlvbnMgaW4gdGhlIGRhdGFzZXQuIAoKYGBge3IsIGZpZy53aWR0aD0xNiwgZmlnLmhlaWdodD02fQojIENhbGN1bGF0ZSBHZW5kZXIgQ291bnRzIChMb2NhdGlvbiBDb3VudHMpCmxvY2F0aW9uX2NvdW50cyA8LSBkYXRhLmZyYW1lKHRhYmxlKGRmJExvY2F0aW9uKSkKbmFtZXMobG9jYXRpb25fY291bnRzKSA8LSBjKCJMb2NhdGlvbiIsICJDb3VudCIpCgojIEZpbHRlciBmb3IgdGhlIHRvcCAyMCBsb2NhdGlvbnMgYnkgY291bnQKdG9wXzIwX2xvY2F0aW9ucyA8LSBsb2NhdGlvbl9jb3VudHMgJT4lCiAgYXJyYW5nZShkZXNjKENvdW50KSkgJT4lCiAgaGVhZCgyMCkKCiMgQ291bnQgUGxvdCBmb3IgVG9wIDIwIExvY2F0aW9ucwpjb3VudF9wbG90IDwtIGdncGxvdCh0b3BfMjBfbG9jYXRpb25zLCBhZXMoeCA9IExvY2F0aW9uLCB5ID0gQ291bnQsIGZpbGwgPSBMb2NhdGlvbikpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIHRoZW1lX3Jvc2VfcGluZSgpICsgICMgQXBwbHkgdGhlIGN1c3RvbSB0aGVtZQogIHNjYWxlX2ZpbGxfcm9zZV9waW5lKCkgKyAgIyBBcHBseSB0aGUgZmlsbCBjb2xvciBzY2FsZQogIGxhYnModGl0bGUgPSAiVG9wIDIwIExvY2F0aW9uIENvdW50IFBsb3QiLCB4ID0gIkxvY2F0aW9uIiwgeSA9ICJDb3VudCIpICsgICMgVGl0bGUgYW5kIGF4aXMgbGFiZWxzCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSAgIyBSb3RhdGUgeC1heGlzIGxhYmVscyBmb3IgYmV0dGVyIHJlYWRhYmlsaXR5CgojIFNob3cgdGhlIGNvdW50IHBsb3QKY291bnRfcGxvdApgYGAKIyMjIE9ic2VydmF0aW9ucwoKIyMjIyAqKkJhciBQbG90KioKLSAqKk9ic2VydmF0aW9uKio6CiAgLSAqKkR1YmxpbioqIGRvbWluYXRlcyB0aGUgZGF0YXNldCwgY29udHJpYnV0aW5nIHRoZSBoaWdoZXN0IGNvdW50IG9mIGN1c3RvbWVycywgc2lnbmlmaWNhbnRseSBzdXJwYXNzaW5nIGFsbCBvdGhlciBsb2NhdGlvbnMuCiAgLSBPdGhlciBsb2NhdGlvbnMgc3VjaCBhcyAqKkNvcmsqKiwgKipHYWx3YXkqKiwgYW5kICoqTGltZXJpY2sqKiBjb250cmlidXRlIGEgc21hbGxlciBidXQgbm90aWNlYWJsZSBjb3VudC4KICAtIEEgbG9uZyB0YWlsIGV4aXN0cyBmb3Igb3RoZXIgbG9jYXRpb25zLCBpbmRpY2F0aW5nIGEgcmVsYXRpdmVseSBzbWFsbCByZXByZXNlbnRhdGlvbiBmcm9tIHRoZXNlIGFyZWFzLgoKIyMjIyAqKlJlY29tbWVuZGF0aW9ucyoqCi0gR2l2ZW4gRHVibGluJ3Mgc2lnbmlmaWNhbnQgY29udHJpYnV0aW9uLCBtYXJrZXRpbmcgYW5kIGJ1c2luZXNzIHN0cmF0ZWdpZXMgc2hvdWxkIGZvY3VzIGhlYXZpbHkgb24gdGhpcyByZWdpb24uCi0gRm9yIHNtYWxsZXIgbG9jYXRpb25zLCBjb25zaWRlciByZWdpb25hbCBjYW1wYWlnbnMgb3IgcHJvbW90aW9ucyB0byBpbmNyZWFzZSBjdXN0b21lciBlbmdhZ2VtZW50LgotIEV2YWx1YXRlIGlmIHRoZSBjb25jZW50cmF0aW9uIGluIER1YmxpbiBza2V3cyB0aGUgZGF0YXNldCBvciBsaW1pdHMgaW5zaWdodHMgaW50byBvdGhlciByZWdpb25zLgoKLS0tCgojIyBCb3ggUGxvdCBBbmFseXNpcwoKVGhpcyBzZWN0aW9uIGV4YW1pbmVzIHRoZSBkaXN0cmlidXRpb24gYW5kIHByZXNlbmNlIG9mIG91dGxpZXJzIGFjcm9zcyBzaXgga2V5IHZhcmlhYmxlcyB1c2luZyBib3ggcGxvdHMuIFRoZSB2aXN1YWxpemF0aW9ucyBwcm92aWRlIGEgY2xlYXIgdW5kZXJzdGFuZGluZyBvZiBkYXRhIHZhcmlhYmlsaXR5IGFuZCBwb3RlbnRpYWwgYW5vbWFsaWVzLgoKYGBge3IsIGZpZy53aWR0aD0xNiwgZmlnLmhlaWdodD04fQojIEdlbmVyYXRlIEJveHBsb3RzIER5bmFtaWNhbGx5CmJveF9wbG90X2xpc3QgPC0gbGFwcGx5KHNlcV9hbG9uZyhkYXRhX2xpc3QpLCBmdW5jdGlvbihpKSB7CiAgZm9ybWF0dGVkX3RpdGxlIDwtIHRvb2xzOjp0b1RpdGxlQ2FzZShnc3ViKCJfIiwgIiAiLCBuYW1lcyhkYXRhX2xpc3QpW2ldKSkKICAKICBnZ3Bsb3QoZGYsIGFlcyh4ID0gZmFjdG9yKDEpLCB5ID0gZGF0YV9saXN0W1tpXV0pKSArCiAgICBnZW9tX2JveHBsb3RfZGVmYXVsdChkYXRhID0gZGYsIHhfY29sID0gImZhY3RvcigxKSIsIHlfY29sID0gbmFtZXMoZGF0YV9saXN0KVtpXSwgZmlsbF9jb2xvciA9IGNvbG9yc1tpXSkgKyAgIyBEeW5hbWljIGNvbG9ycwogICAgbGFicygKICAgICAgdGl0bGUgPSBwYXN0ZSgiIiwgZm9ybWF0dGVkX3RpdGxlKSwKICAgICAgeCA9ICJDYXRlZ29yeSIsIAogICAgICB5ID0gIlZhbHVlIgogICAgKSArCiAgICB0aGVtZV9yb3NlX3BpbmUoKSArICAjIEFwcGx5IFJvc2UgUGluZSB0aGVtZQogICAgdGhlbWUoCiAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCAgIyBSZW1vdmUgeC1heGlzIHRleHQKICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAgIyBSZW1vdmUgeC1heGlzIHRpY2tzCiAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSAgIyBUaXRsZSBmb3JtYXR0aW5nCiAgICApCn0pCgojIENvbWJpbmUgQWxsIEJveHBsb3RzIGludG8gYSBHcmlkIExheW91dCB1c2luZyBncmlkLmFycmFuZ2UKZ3JpZC5hcnJhbmdlKGdyb2JzID0gYm94X3Bsb3RfbGlzdCwgbmNvbCA9IGxlbmd0aChzZWxlY3RlZF9jb2xzKSkgICMgQXJyYW5nZSBwbG90cyBob3Jpem9udGFsbHkKYGBgCiMjIyBPYnNlcnZhdGlvbnMKCiMjIyMgKioxLiBGaXJzdCBPcmRlciBQcm9maXQqKgotICoqT2JzZXJ2YXRpb24qKjoKICAtIFRoZSBtYWpvcml0eSBvZiBkYXRhIHBvaW50cyBhcmUgY2x1c3RlcmVkIGJlbG93ICoqMjAgdW5pdHMqKi4KICAtIEEgZmV3IGV4dHJlbWUgb3V0bGllcnMgZXh0ZW5kIHVwIHRvICoqNjAgdW5pdHMqKiwgc3VnZ2VzdGluZyBhIHNrZXdlZCBkaXN0cmlidXRpb24uCgojIyMjICoqMi4gU3Vic2VxdWVudCBPcmRlciBQcm9maXQqKgotICoqT2JzZXJ2YXRpb24qKjoKICAtIE1vc3QgdmFsdWVzIGFyZSBiZWxvdyAqKjEwMCB1bml0cyoqLCB3aXRoIHNpZ25pZmljYW50IG91dGxpZXJzIHJlYWNoaW5nIGJleW9uZCAqKjQwMCB1bml0cyoqLgogIC0gSGlnaGxpZ2h0cyB2YXJpYWJpbGl0eSBpbiBwcm9maXQgZ2VuZXJhdGlvbiBmcm9tIHN1YnNlcXVlbnQgb3JkZXJzLgoKIyMjIyAqKjMuIFN1YnNlcXVlbnQgT3JkZXJzIENvdW50KioKLSAqKk9ic2VydmF0aW9uKio6CiAgLSBEYXRhIGlzIHRpZ2h0bHkgY2x1c3RlcmVkIGJlbG93ICoqMTAgb3JkZXJzKiosIHdpdGggYSBmZXcgb3V0bGllcnMgZXhjZWVkaW5nICoqMzAgb3JkZXJzKiouCgojIyMjICoqNC4gVG90YWwgVmFsdWUgb2YgQWxsIFByb21vdGlvbnMqKgotICoqT2JzZXJ2YXRpb24qKjoKICAtIFRoZSBtYWpvcml0eSBvZiBkYXRhIHBvaW50cyBmYWxsIGJlbG93ICoqMjAgdW5pdHMqKi4KICAtIE51bWVyb3VzIGV4dHJlbWUgb3V0bGllcnMgZXhjZWVkICoqMTAwIHVuaXRzKiosIGluZGljYXRpbmcgcHJvbW90aW9uYWwgYW5vbWFsaWVzLgoKIyMjIyAqKjUuIEFnZSoqCi0gKipPYnNlcnZhdGlvbioqOgogIC0gTW9zdCBjdXN0b21lcnMgYXJlIGJldHdlZW4gKioyMCBhbmQgNTAgeWVhcnMgb2xkKiouCiAgLSBGZXcgb3V0bGllcnMgZXh0ZW5kIGJleW9uZCAqKjgwIHllYXJzKiosIHdoaWNoIGNvdWxkIGluZGljYXRlIGRhdGEgaW5jb25zaXN0ZW5jaWVzIG9yIG9sZGVyIGN1c3RvbWVycy4KCiMjIyMgKio2LiBUb3RhbCBQcm9maXQqKgotICoqT2JzZXJ2YXRpb24qKjoKICAtIFRoZSBidWxrIG9mIGRhdGEgaXMgYmVsb3cgKioxMDAgdW5pdHMqKi4KICAtIE91dGxpZXJzIGV4dGVuZCBiZXlvbmQgKio0MDAgdW5pdHMqKiwgcG9pbnRpbmcgdG8gaGlnaC12YWx1ZSBjdXN0b21lcnMgb3IgZGF0YSBpcnJlZ3VsYXJpdGllcy4KCi0tLQoKIyMjIFJlY29tbWVuZGF0aW9ucwotICoqT3V0bGllciBUcmVhdG1lbnQqKjogQ29uc2lkZXIgd2luc29yaXppbmcgb3IgdHJhbnNmb3JtaW5nIGRhdGEgdG8gbWluaW1pemUgdGhlIGluZmx1ZW5jZSBvZiBleHRyZW1lIG91dGxpZXJzLgotICoqRGF0YSBRdWFsaXR5IENoZWNrKio6IFZlcmlmeSB0aGUgYWNjdXJhY3kgb2Ygb3V0bGllciBkYXRhIHBvaW50cywgcGFydGljdWxhcmx5IGZvciB2YXJpYWJsZXMgbGlrZSAqKkFnZSoqIGFuZCAqKlRvdGFsIFZhbHVlIG9mIEFsbCBQcm9tb3Rpb25zKiouCi0gKipGdXJ0aGVyIEFuYWx5c2lzKio6IEludmVzdGlnYXRlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBoaWdoIG91dGxpZXJzIGluICoqVG90YWwgUHJvZml0KiogYW5kIHByb21vdGlvbmFsIGFjdGl2aXR5LgoKLS0tCgojIyBBZ2UgRGlzdHJpYnV0aW9uIGJ5IEdlbmRlcgoKVGhpcyBzZWN0aW9uIGV4cGxvcmVzIHRoZSBkaXN0cmlidXRpb24gb2YgY3VzdG9tZXJzJyBhZ2VzLCBzZWdtZW50ZWQgYnkgZ2VuZGVyLiBUaGUgdmlzdWFsaXphdGlvbiBwcm92aWRlcyBpbnNpZ2h0cyBpbnRvIGFnZSBkZW1vZ3JhcGhpY3MgYW5kIGhpZ2hsaWdodHMgZ2VuZGVyLWJhc2VkIHRyZW5kcy4KCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9Nn0KZ2dwbG90KGRmLCBhZXMoeCA9IEFnZSwgZmlsbCA9IEdlbmRlcikpICsgIyBHcm91cCBieSBHZW5kZXIKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuNSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkFnZSBEaXN0cmlidXRpb24gYnkgR2VuZGVyIiwKICAgIHggPSAiQWdlIiwKICAgIHkgPSAiRnJlcXVlbmN5IgogICkgKwogIHRoZW1lX3Jvc2VfcGluZSgpICsKICBzY2FsZV9maWxsX3Jvc2VfcGluZSgpCmBgYAojIyMgT2JzZXJ2YXRpb25zCgoxLiAqKk92ZXJhbGwgQWdlIERpc3RyaWJ1dGlvbioqOgogICAtIFRoZSBtYWpvcml0eSBvZiBjdXN0b21lcnMgZmFsbCBiZXR3ZWVuICoqMjAgYW5kIDUwIHllYXJzKiouCiAgIC0gQSBzaGFycCBkZWNsaW5lIGluIGZyZXF1ZW5jeSBpcyBvYnNlcnZlZCBiZXlvbmQgKio1MCB5ZWFycyoqLCB3aXRoIHNwYXJzZSBkYXRhIGZvciBjdXN0b21lcnMgYWJvdmUgKio4MCB5ZWFycyoqLgoKMi4gKipHZW5kZXIgU2VnbWVudGF0aW9uKio6CiAgIC0gKipNYWxlcyoqIGZvcm0gdGhlIGxhcmdlc3Qgc2VnbWVudCBhY3Jvc3MgYWxsIGFnZSBncm91cHMuCiAgIC0gKipGZW1hbGVzKiogcmVwcmVzZW50IGEgc21hbGxlciBidXQgY29uc2lzdGVudCBwb3J0aW9uIGFjcm9zcyBtb3N0IGFnZSBncm91cHMuCiAgIC0gKipOb3QgRGVmaW5lZCoqIGdlbmRlciBoYXMgbWluaW1hbCByZXByZXNlbnRhdGlvbiwgcHJpbWFyaWx5IGluIHRoZSB5b3VuZ2VyIGFnZSBicmFja2V0cy4KCjMuICoqUGVha3MgaW4gQWdlKio6CiAgIC0gTm90aWNlYWJsZSBwZWFrcyBhcm91bmQgKioyNeKAkzMwIHllYXJzKiosIGluZGljYXRpbmcgYSBjb25jZW50cmF0ZWQgY3VzdG9tZXIgYmFzZSBpbiB0aGlzIGFnZSByYW5nZS4KCi0tLQoKIyMjIFJlY29tbWVuZGF0aW9ucwotICoqVGFyZ2V0IE1hcmtldGluZyoqOgogIC0gRm9jdXMgbWFya2V0aW5nIGVmZm9ydHMgb24gdGhlICoqMjAtNTAgYWdlIGdyb3VwKiosIGFzIGl0IGNvbnN0aXR1dGVzIHRoZSBtYWpvcml0eSBvZiB0aGUgY3VzdG9tZXIgYmFzZS4KLSAqKkRhdGEgVmFsaWRhdGlvbioqOgogIC0gRW5zdXJlIGFjY3VyYWN5IGZvciBjdXN0b21lcnMgd2l0aCB1bmRlZmluZWQgZ2VuZGVyIG9yIHRob3NlIGFib3ZlICoqODAgeWVhcnMqKiwgYXMgdGhlc2UgbWF5IGluZGljYXRlIGRhdGEgZW50cnkgZXJyb3JzLgoKLS0tCgojIyBJbXBhY3Qgb2YgUHJvbW90aW9ucyBvbiBUb3RhbCBQcm9maXQKClRoaXMgc2VjdGlvbiBpbnZlc3RpZ2F0ZXMgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSAqKlRvdGFsIFZhbHVlIG9mIEFsbCBQcm9tb3Rpb25zKiogYW5kICoqVG90YWwgUHJvZml0KiouIFRoZSBzY2F0dGVyIHBsb3QgcHJvdmlkZXMgYSB2aXN1YWwgcmVwcmVzZW50YXRpb24gb2YgaG93IHByb21vdGlvbmFsIGFjdGl2aXRpZXMgaW5mbHVlbmNlIGN1c3RvbWVyIHByb2ZpdGFiaWxpdHkuCgpgYGB7ciwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTZ9CmdncGxvdChkZiwgYWVzKHggPSBUb3RhbF92YWx1ZV9vZl9hbGxfcHJvbW90aW9ucywgeSA9IFRvdGFsX1Byb2ZpdCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9IGNvbG9yc1sxXSkgKyAgIyBDdXN0b20gY29sb3IgZm9yIHBvaW50cyBmcm9tIFJvc8OpIFBpbmUgcGFsZXR0ZQogIGxhYnMoCiAgICB0aXRsZSA9ICJJbXBhY3Qgb2YgUHJvbW90aW9ucyBvbiBUb3RhbCBQcm9maXQiLCAKICAgIHggPSAiVG90YWwgVmFsdWUgb2YgQWxsIFByb21vdGlvbnMiLCAKICAgIHkgPSAiVG90YWwgUHJvZml0IgogICkgKwogIHRoZW1lX3Jvc2VfcGluZSgpICAjIEFwcGx5IHRoZSBjdXN0b20gUm9zw6kgUGluZSB0aGVtZQpgYGAKIyMjIE9ic2VydmF0aW9ucwoKMS4gKipPdmVyYWxsIFJlbGF0aW9uc2hpcCoqOgogICAtIEEgcG9zaXRpdmUgdHJlbmQgaXMgZXZpZGVudCwgaW5kaWNhdGluZyB0aGF0IGhpZ2hlciBwcm9tb3Rpb25hbCB2YWx1ZXMgYXJlIGFzc29jaWF0ZWQgd2l0aCBpbmNyZWFzZWQgdG90YWwgcHJvZml0LgogICAtIEhvd2V2ZXIsIHRoZSBzY2F0dGVycGxvdCBzdWdnZXN0cyAqKmRpbWluaXNoaW5nIHJldHVybnMqKiBhcyBwcm9tb3Rpb25zIGV4Y2VlZCBhIGNlcnRhaW4gdmFsdWUuCgoyLiAqKkNvbmNlbnRyYXRpb24gb2YgRGF0YSoqOgogICAtIEEgbGFyZ2UgY2x1c3RlciBvZiBkYXRhIHBvaW50cyBsaWVzIGluIHRoZSBsb3dlciByYW5nZSBvZiAqKlRvdGFsIFZhbHVlIG9mIEFsbCBQcm9tb3Rpb25zKiosIHN1Z2dlc3RpbmcgdGhhdCBtb3N0IGN1c3RvbWVycyByZWNlaXZlIGxpbWl0ZWQgcHJvbW90aW9ucy4KICAgLSBUaGUgKipUb3RhbCBQcm9maXQqKiBzaG93cyBzaWduaWZpY2FudCB2YXJpYWJpbGl0eSwgZXZlbiBmb3IgY3VzdG9tZXJzIHdpdGggbG93IHByb21vdGlvbmFsIHZhbHVlcy4KCjMuICoqT3V0bGllcnMqKjoKICAgLSBBIGZldyBjdXN0b21lcnMgZXhoaWJpdCBleHRyZW1lbHkgaGlnaCBwcm9tb3Rpb25hbCB2YWx1ZXMgd2l0aCB2YXJ5aW5nIHByb2ZpdCBsZXZlbHMuIFRoZXNlIHBvaW50cyBjb3VsZCByZXByZXNlbnQgc3BlY2lhbCBjYXNlcyBsaWtlIGJ1bGsgZGlzY291bnRzIG9yIGxveWFsdHkgcmV3YXJkcy4KCjQuICoqUG90ZW50aWFsIE5vbi1MaW5lYXIgUGF0dGVybioqOgogICAtIEJleW9uZCBhIGNlcnRhaW4gcHJvbW90aW9uYWwgdGhyZXNob2xkLCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gcHJvbW90aW9ucyBhbmQgcHJvZml0IGFwcGVhcnMgbGVzcyBsaW5lYXIsIGhpZ2hsaWdodGluZyB0aGUgbmVlZCBmb3IgZnVydGhlciBpbnZlc3RpZ2F0aW9uLgoKLS0tCgojIyMgUmVjb21tZW5kYXRpb25zCgotICoqT3B0aW1pemluZyBQcm9tb3Rpb25hbCBTdHJhdGVneSoqOgogIC0gSWRlbnRpZnkgdGhlIHRocmVzaG9sZCBmb3IgZGltaW5pc2hpbmcgcmV0dXJucyBhbmQgb3B0aW1pemUgcHJvbW90aW9uYWwgc3BlbmRpbmcgdG8gbWF4aW1pemUgcHJvZml0YWJpbGl0eS4KLSAqKkN1c3RvbWVyIFNlZ21lbnRhdGlvbioqOgogIC0gQW5hbHl6ZSBzZWdtZW50cyBvZiBjdXN0b21lcnMgd2l0aCBoaWdoIHByb21vdGlvbnMgYnV0IGxvdyBwcm9maXRzIHRvIGltcHJvdmUgdGFyZ2V0ZWQgbWFya2V0aW5nIGVmZm9ydHMuCi0gKipPdXRsaWVyIEFuYWx5c2lzKio6CiAgLSBJbnZlc3RpZ2F0ZSBvdXRsaWVycyB3aXRoIGV4dHJlbWVseSBoaWdoIHByb21vdGlvbmFsIHZhbHVlcyB0byB1bmRlcnN0YW5kIHVuaXF1ZSBjYXNlcyBhbmQgcmVmaW5lIG1hcmtldGluZyBzdHJhdGVnaWVzLgoKLS0tCgojIyBUb3RhbCBQcm9maXQgQW5hbHlzaXMKClRoaXMgc2VjdGlvbiBwcm92aWRlcyBpbnNpZ2h0cyBpbnRvIHRvdGFsIHByb2ZpdCBkaXN0cmlidXRpb24gYWNyb3NzIGRpZmZlcmVudCBnZW5kZXJzIGFuZCBtYXJrZXRpbmcgY2hhbm5lbCB0eXBlcy4gVGhlIGJhciBwbG90cyBpbGx1c3RyYXRlIHRoZSB0b3RhbCBwcm9maXQgY29udHJpYnV0aW9uIGZvciBlYWNoIGNhdGVnb3J5LgoKYGBge3IsIGZpZy53aWR0aD0xOSwgZmlnLmhlaWdodD05fQojIFBsb3QgMTogVG90YWwgUHJvZml0IGJ5IEdlbmRlcgpwbG90X2dlbmRlciA8LSBnZ3Bsb3QoZGYsIGFlcyh4ID0gZmFjdG9yKEdlbmRlciksIHkgPSBUb3RhbF9Qcm9maXQsIGZpbGwgPSBHZW5kZXIpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJzdW1tYXJ5IiwgZnVuID0gInN1bSIsIHBvc2l0aW9uID0gImRvZGdlIikgKyAgIyBTdW1taW5nIHRoZSB0b3RhbCBwcm9maXQgYnkgZ2VuZGVyCiAgbGFicygKICAgIHRpdGxlID0gIlRvdGFsIFByb2ZpdCBieSBHZW5kZXIiLAogICAgeCA9ICJHZW5kZXIiLAogICAgeSA9ICJUb3RhbCBQcm9maXQiCiAgKSArCiAgdGhlbWVfcm9zZV9waW5lKCkgKyAKICBzY2FsZV9maWxsX3Jvc2VfcGluZSgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikgICMgTW92ZSBsZWdlbmQgdG8gdGhlIHRvcAoKIyBQbG90IDI6IFRvdGFsIFByb2ZpdCBieSBNYXJrZXRpbmcgQ2hhbm5lbCBUeXBlCnBsb3RfbWFya2V0aW5nX2NoYW5uZWwgPC0gZ2dwbG90KGRmLCBhZXMoeCA9IGZhY3RvcihNYXJrZXRpbmdfQ2hhbm5lbF9UeXBlKSwgeSA9IFRvdGFsX1Byb2ZpdCwgZmlsbCA9IE1hcmtldGluZ19DaGFubmVsX1R5cGUpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJzdW1tYXJ5IiwgZnVuID0gInN1bSIsIHBvc2l0aW9uID0gImRvZGdlIikgKyAgIyBTdW1taW5nIHRvdGFsIHByb2ZpdCBieSBtYXJrZXRpbmcgY2hhbm5lbAogIGxhYnMoCiAgICB0aXRsZSA9ICJUb3RhbCBQcm9maXQgYnkgTWFya2V0aW5nIENoYW5uZWwgVHlwZSIsCiAgICB4ID0gIk1hcmtldGluZyBDaGFubmVsIFR5cGUiLAogICAgeSA9ICJUb3RhbCBQcm9maXQiCiAgKSArCiAgdGhlbWVfcm9zZV9waW5lKCkgKwogIHNjYWxlX2ZpbGxfcm9zZV9waW5lKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKSAgIyBNb3ZlIGxlZ2VuZCB0byB0aGUgdG9wCgoKIyBNZXJnZSB0aGUgdGhyZWUgcGxvdHMgaW50byBhIGdyaWQgbGF5b3V0CmdyaWQuYXJyYW5nZShwbG90X2dlbmRlciwgcGxvdF9tYXJrZXRpbmdfY2hhbm5lbCwgbmNvbCA9IDIpCgpgYGAKIyMjIE9ic2VydmF0aW9ucwoKIyMjIyBUb3RhbCBQcm9maXQgYnkgR2VuZGVyCjEuICoqTWFsZSBEb21pbmFuY2UqKjoKICAgLSBNYWxlIGN1c3RvbWVycyBjb250cmlidXRlIHRoZSBoaWdoZXN0IHRvdGFsIHByb2ZpdCBjb21wYXJlZCB0byBvdGhlciBnZW5kZXJzLgogICAtIEZlbWFsZSBjdXN0b21lcnMgc2hvdyBhIHNpZ25pZmljYW50IGNvbnRyaWJ1dGlvbiwgYnV0IGl0IGlzIHN0aWxsIG11Y2ggbG93ZXIgdGhhbiBtYWxlIGN1c3RvbWVycy4KICAgLSBUaGUgIk5vdCBEZWZpbmVkIiBnZW5kZXIgY2F0ZWdvcnkgY29udHJpYnV0ZXMgdGhlIGxlYXN0IHRvIHRvdGFsIHByb2ZpdC4KCjIuICoqSW1wbGljYXRpb25zKio6CiAgIC0gTWFya2V0aW5nIHN0cmF0ZWdpZXMgdGFyZ2V0ZWQgdG93YXJkIG1hbGUgY3VzdG9tZXJzIGFwcGVhciB0byBiZSBtb3JlIGVmZmVjdGl2ZSBpbiBkcml2aW5nIHByb2ZpdHMuCiAgIC0gUG90ZW50aWFsIG9wcG9ydHVuaXR5IGV4aXN0cyB0byBpbXByb3ZlIGVuZ2FnZW1lbnQgYW5kIHByb2ZpdCBnZW5lcmF0aW9uIGZyb20gZmVtYWxlIGN1c3RvbWVycy4KCi0tLQoKIyMjIyBUb3RhbCBQcm9maXQgYnkgTWFya2V0aW5nIENoYW5uZWwgVHlwZQoxLiAqKlRvcCBQZXJmb3JtaW5nIENoYW5uZWxzKio6CiAgIC0gKipEaXJlY3QgbWFya2V0aW5nKiogY2hhbm5lbHMgY29udHJpYnV0ZSB0aGUgaGlnaGVzdCB0b3RhbCBwcm9maXQsIHNob3djYXNpbmcgdGhlIGVmZmVjdGl2ZW5lc3Mgb2YgZGlyZWN0IGN1c3RvbWVyIGVuZ2FnZW1lbnQuCiAgIC0gKipPcmdhbmljIHNlYXJjaCoqIGFsc28gcGVyZm9ybXMgd2VsbCwgaW5kaWNhdGluZyB0aGUgaW1wb3J0YW5jZSBvZiBTRU8gYW5kIG9yZ2FuaWMgdHJhZmZpYyBpbiBkcml2aW5nIHByb2ZpdGFiaWxpdHkuCgoyLiAqKlVuZGVycGVyZm9ybWluZyBDaGFubmVscyoqOgogICAtICoqUGFpZCBzb2NpYWwqKiBhbmQgKiphZmZpbGlhdGVzKiogY2hhbm5lbHMgY29udHJpYnV0ZSB0aGUgbGVhc3QgdG8gdG90YWwgcHJvZml0LCBzdWdnZXN0aW5nIHRoZSBuZWVkIHRvIGV2YWx1YXRlIGFuZCBvcHRpbWl6ZSBzdHJhdGVnaWVzIGZvciB0aGVzZSBjaGFubmVscy4KCjMuICoqSW1wbGljYXRpb25zKio6CiAgIC0gQnVzaW5lc3NlcyBzaG91bGQgcHJpb3JpdGl6ZSByZXNvdXJjZXMgb24gZGlyZWN0IGFuZCBvcmdhbmljIGNoYW5uZWxzIGZvciBtYXhpbXVtIHByb2ZpdGFiaWxpdHkuCiAgIC0gRXZhbHVhdGUgdGhlIFJPSSBmb3IgdW5kZXJwZXJmb3JtaW5nIGNoYW5uZWxzIGFuZCByZWZpbmUgc3RyYXRlZ2llcyBmb3IgYmV0dGVyIHJldHVybnMuCgotLS0KCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9Nn0KIyBTdW1tYXJpemUgcGVyY2VudGFnZSBkaXN0cmlidXRpb24gb2YgR2VuZGVyIGJ5IE1hcmtldGluZ19DaGFubmVsX1R5cGUKZ2VuZGVyX2NoYW5uZWxfZGlzdHJpYnV0aW9uIDwtIGRmICU+JQogIGdyb3VwX2J5KEdlbmRlciwgTWFya2V0aW5nX0NoYW5uZWxfVHlwZSkgJT4lCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUKICBncm91cF9ieShNYXJrZXRpbmdfQ2hhbm5lbF9UeXBlKSAlPiUKICBtdXRhdGUoUGVyY2VudGFnZSA9IChDb3VudCAvIHN1bShDb3VudCkpICogMTAwKQoKIyBQaXZvdCBkYXRhIGZvciBoZWF0bWFwCmhlYXRtYXBfZGF0YSA8LSBnZW5kZXJfY2hhbm5lbF9kaXN0cmlidXRpb24gJT4lCiAgc2VsZWN0KEdlbmRlciwgTWFya2V0aW5nX0NoYW5uZWxfVHlwZSwgUGVyY2VudGFnZSkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IE1hcmtldGluZ19DaGFubmVsX1R5cGUsIHZhbHVlc19mcm9tID0gUGVyY2VudGFnZSkKCiMgTWVsdCB0aGUgZGF0YSBmb3IgZ2dwbG90Cm1lbHRlZF9kYXRhIDwtIG1lbHQoaGVhdG1hcF9kYXRhLCBpZC52YXJzID0gIkdlbmRlciIsIHZhcmlhYmxlLm5hbWUgPSAiTWFya2V0aW5nX0NoYW5uZWxfVHlwZSIsIHZhbHVlLm5hbWUgPSAiUGVyY2VudGFnZSIpCgojIFBsb3QgdGhlIGhlYXRtYXAgd2l0aCBgc2NhbGVfZmlsbF9ncmFkaWVudDJgIGFuZCBSb3PDqSBQaW5lIGNvbG9ycwpnZ3Bsb3QobWVsdGVkX2RhdGEsIGFlcyh4ID0gTWFya2V0aW5nX0NoYW5uZWxfVHlwZSwgeSA9IEdlbmRlciwgZmlsbCA9IFBlcmNlbnRhZ2UpKSArCiAgZ2VvbV90aWxlKCkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBzcHJpbnRmKCIlLjJmIiwgUGVyY2VudGFnZSkpLCBzaXplID0gNikgKyAjIFNob3cgcGVyY2VudGFnZSB3aXRoIDIgZGVjaW1hbHMKICBzY2FsZV9maWxsX2dyYWRpZW50MigKICAgIGxvdyA9IGNvbG9yc1syXSwgICAgICAjIExvdyBjb2xvcgogICAgbWlkID0gY29sb3JzWzFdLCAgICAgICMgTWlkIGNvbG9yCiAgICBoaWdoID0gY29sb3JzWzNdLCAgICAgIyBIaWdoIGNvbG9yCiAgICBtaWRwb2ludCA9IDAuNSwgICAgICAgICMgQXNzdW1lIG1pZHBvaW50IGF0IDUwJSBmb3IgcGVyY2VudGFnZXMKICAgIGxpbWl0cyA9IGMoMCwgMTAwKSwgICAjIFNldCBsaW1pdHMgZm9yIHBlcmNlbnRhZ2UKICAgIG5hbWUgPSAiUGVyY2VudGFnZSIKICApICsKICB0aGVtZV9yb3NlX3BpbmUoYmFzZV9zaXplID0gMTQpICsKICBsYWJzKAogICAgdGl0bGUgPSAiR2VuZGVyIERpc3RyaWJ1dGlvbiBieSBNYXJrZXRpbmcgQ2hhbm5lbCBUeXBlICglKSIsCiAgICB4ID0gIk1hcmtldGluZyBDaGFubmVsIFR5cGUiLAogICAgeSA9ICJHZW5kZXIiCiAgKSArCiAgdGhlbWUoCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTkpLCAjIENlbnRlciBhbmQgYm9sZCB0aXRsZQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCBzaXplID0gMTQpLCAjIFJvdGF0ZSB4LWF4aXMgbGFiZWxzCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLCAjIEFkanVzdCB5LWF4aXMgdGV4dCBzaXplCiAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLCAjIFBsYWNlIHRoZSBsZWdlbmQgb24gdGhlIHJpZ2h0CiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLCAjIEFkanVzdCBsZWdlbmQgdGV4dCBzaXplCiAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEzLCBmYWNlID0gImJvbGQiKSAjIEFkanVzdCBsZWdlbmQgdGl0bGUgc2l6ZQogICkKYGBgCgoKIyMgSW1wYWN0IG9mIFByb21vdGlvbnMgYW5kIE1hcmtldGluZyBDaGFubmVscyBvbiBUb3RhbCBQcm9maXQKClRoaXMgc2VjdGlvbiBleHBsb3JlcyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHRvdGFsIHZhbHVlIG9mIHByb21vdGlvbnMgYW5kIHRvdGFsIHByb2ZpdCBhY3Jvc3MgdmFyaW91cyBtYXJrZXRpbmcgY2hhbm5lbHMuIFRoZSBzY2F0dGVyIHBsb3QgaGlnaGxpZ2h0cyB0aGUgaW1wYWN0IG9mIG1hcmtldGluZyBlZmZvcnRzIGFuZCBwcm9tb3Rpb25zIG9uIGN1c3RvbWVyIHByb2ZpdGFiaWxpdHkuCgpgYGB7ciwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTZ9CmdncGxvdChkZiwgYWVzKHggPSBUb3RhbF92YWx1ZV9vZl9hbGxfcHJvbW90aW9ucywgeSA9IFRvdGFsX1Byb2ZpdCwgY29sb3IgPSBNYXJrZXRpbmdfQ2hhbm5lbF9UeXBlKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDMpICsgICMgQWRkIHBvaW50cyB3aXRoIHNpemUKICBsYWJzKAogICAgdGl0bGUgPSAiSW1wYWN0IG9mIFByb21vdGlvbnMgYW5kIE1hcmtldGluZyBDaGFubmVscyBvbiBUb3RhbCBQcm9maXQiLCAKICAgIHggPSAiVG90YWwgVmFsdWUgb2YgQWxsIFByb21vdGlvbnMiLCAKICAgIHkgPSAiVG90YWwgUHJvZml0IgogICkgKwogIHRoZW1lX3Jvc2VfcGluZSgpICsgICMgQXBwbHkgdGhlIGN1c3RvbSBSb3PDqSBQaW5lIHRoZW1lCiAgc2NhbGVfY29sb3Jfcm9zZV9waW5lKCkgICMgQXBwbHkgdGhlIFJvc8OpIFBpbmUgY29sb3Igc2NhbGUgdG8gdGhlIGNvbG9yIGFlc3RoZXRpYwpgYGAKIyMjIE9ic2VydmF0aW9ucwoKMS4gKipHZW5lcmFsIFRyZW5kKio6CiAgIC0gQXMgdGhlIHRvdGFsIHZhbHVlIG9mIHByb21vdGlvbnMgaW5jcmVhc2VzLCB0aGVyZSBpcyBhIGNvcnJlc3BvbmRpbmcgaW5jcmVhc2UgaW4gdG90YWwgcHJvZml0LCBidXQgdGhlIHJlbGF0aW9uc2hpcCBhcHBlYXJzIG5vbi1saW5lYXIuCiAgIC0gVGhlIG1ham9yaXR5IG9mIGRhdGEgcG9pbnRzIGNsdXN0ZXIgaW4gdGhlIGxvd2VyIHJhbmdlcyBvZiBwcm9tb3Rpb24gdmFsdWUgYW5kIHByb2ZpdCwgaW5kaWNhdGluZyBtYW55IGN1c3RvbWVycyBnZW5lcmF0ZSBtb2Rlc3QgcHJvZml0cyB3aXRoIGxpbWl0ZWQgcHJvbW90aW9uIGV4cGVuZGl0dXJlLgoKMi4gKipDaGFubmVsLXdpc2UgSW5zaWdodHMqKjoKICAgLSAqKkFmZmlsaWF0ZXMqKiBhbmQgKipQYWlkIFNvY2lhbCoqIHRlbmQgdG8gaGF2ZSBsb3dlciBwcm9maXQgY29udHJpYnV0aW9ucyBldmVuIHdpdGggaW5jcmVhc2VkIHByb21vdGlvbiB2YWx1ZXMuCiAgIC0gKipEaXJlY3QqKiBhbmQgKipPcmdhbmljIFNlYXJjaCoqIGNoYW5uZWxzIGV4aGliaXQgbW9yZSBjb25zaXN0ZW50IHByb2ZpdCBnZW5lcmF0aW9uLCBlc3BlY2lhbGx5IGF0IGhpZ2hlciBwcm9tb3Rpb24gdmFsdWVzLgogICAtICoqUGFpZCBTZWFyY2gqKiBkaXNwbGF5cyBhIHNjYXR0ZXJlZCBwYXR0ZXJuIHdpdGggc29tZSBoaWdoLXByb2ZpdCBvdXRsaWVycy4KCjMuICoqT3V0bGllcnMqKjoKICAgLSBDZXJ0YWluIGN1c3RvbWVycyBhY2hpZXZlIGhpZ2ggcHJvZml0cyB3aXRob3V0IHN1YnN0YW50aWFsIHByb21vdGlvbiB2YWx1ZSwgc3VnZ2VzdGluZyBoaWdoLXZhbHVlIGN1c3RvbWVycyBtYXkgcmVxdWlyZSBsZXNzIGluY2VudGl2ZS4KICAgLSBTb21lIGhpZ2ggcHJvbW90aW9uIHZhbHVlcyBkbyBub3QgY29ycmVzcG9uZCB0byBoaWdoIHByb2ZpdHMsIHNpZ25hbGluZyBpbmVmZmljaWVudCBwcm9tb3Rpb24gc3RyYXRlZ2llcy4KCi0tLQoKIyMjIFJlY29tbWVuZGF0aW9ucwoKLSBGb2N1cyBtYXJrZXRpbmcgZWZmb3J0cyBvbiAqKkRpcmVjdCoqIGFuZCAqKk9yZ2FuaWMgU2VhcmNoKiogY2hhbm5lbHMgdG8gbWF4aW1pemUgcHJvZml0YWJpbGl0eS4KLSBSZWFzc2VzcyBwcm9tb3Rpb24gc3RyYXRlZ2llcyBmb3IgKipBZmZpbGlhdGVzKiogYW5kICoqUGFpZCBTb2NpYWwqKiB0byBpbXByb3ZlIHRoZWlyIHJldHVybiBvbiBpbnZlc3RtZW50IChST0kpLgotIEludmVzdGlnYXRlIGhpZ2gtcHJvZml0IGN1c3RvbWVycyB3aXRoIG1pbmltYWwgcHJvbW90aW9ucyB0byBpZGVudGlmeSBjaGFyYWN0ZXJpc3RpY3MgdGhhdCBjYW4gZ3VpZGUgY3VzdG9tZXIgc2VnbWVudGF0aW9uIGFuZCB0YXJnZXRlZCBjYW1wYWlnbnMuCgotLS0KCiMjIExvY2F0aW9ucyBieSBUb3RhbCBQcm9maXQgYW5kIEdlbmRlcgoKVGhpcyB2aXN1YWxpemF0aW9uIHByZXNlbnRzIHRoZSBkaXN0cmlidXRpb24gb2YgdG90YWwgcHJvZml0IGFjcm9zcyBkaWZmZXJlbnQgbG9jYXRpb25zLCBzZWdtZW50ZWQgYnkgZ2VuZGVyIChGZW1hbGUsIE1hbGUsIGFuZCBOb3QgRGVmaW5lZCkuIFRoZSBhbmFseXNpcyBmb2N1c2VzIG9uIGlkZW50aWZ5aW5nIGdlb2dyYXBoaWNhbCBwcm9maXRhYmlsaXR5IHRyZW5kcy4KCmBgYHtyLCBmaWcud2lkdGg9MTgsIGZpZy5oZWlnaHQ9MTB9CiMgU3VtbWFyaXplIHRoZSB0b3RhbCBwcm9maXQgYnkgTG9jYXRpb24KbG9jYXRpb25fcmV2IDwtIGRmICU+JQogIGdyb3VwX2J5KExvY2F0aW9uKSAlPiUKICBzdW1tYXJpc2UoVG90YWxfUHJvZml0ID0gc3VtKFRvdGFsX1Byb2ZpdCwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShUb3RhbF9Qcm9maXQpICU+JQogIG11dGF0ZShMb2NhdGlvbiA9IGZhY3RvcihMb2NhdGlvbiwgbGV2ZWxzID0gLiRMb2NhdGlvbikpCgojIFN1bW1hcml6ZSB0b3RhbCBwcm9maXQgYnkgTG9jYXRpb24gYW5kIEdlbmRlcgpsb2NhdGlvbl9nZW5kZXJfcmV2IDwtIGRmICU+JQogIGdyb3VwX2J5KExvY2F0aW9uLCBHZW5kZXIpICU+JQogIHN1bW1hcmlzZShUb3RhbF9Qcm9maXQgPSBzdW0oVG90YWxfUHJvZml0LCBuYS5ybSA9IFRSVUUpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKExvY2F0aW9uID0gZmFjdG9yKExvY2F0aW9uLCBsZXZlbHMgPSBsb2NhdGlvbl9yZXYkTG9jYXRpb24pKQoKIyBQbG90IHRoZSBkYXRhIGZvciB0aGUgdG9wIDIwIGxvY2F0aW9ucyBieSBUb3RhbF9Qcm9maXQgYW5kIEdlbmRlcgpnZ3Bsb3QobG9jYXRpb25fZ2VuZGVyX3JldiwgYWVzKExvY2F0aW9uLCBUb3RhbF9Qcm9maXQsIGZpbGwgPSBHZW5kZXIpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKwogIGNvb3JkX2ZsaXAoKSArCiAgZmFjZXRfd3JhcCh+IEdlbmRlcikgKwogIHRoZW1lX3Jvc2VfcGluZSgpICsgICMgQXBwbHkgdGhlIGN1c3RvbSB0aGVtZQogIHNjYWxlX2ZpbGxfcm9zZV9waW5lKCkgKyAgIyBBcHBseSB0aGUgZmlsbCBjb2xvciBzY2FsZQogIGxhYnModGl0bGUgPSAiTG9jYXRpb25zIGJ5IFRvdGFsIFByb2ZpdCBhbmQgR2VuZGVyIiwgeCA9ICJMb2NhdGlvbiIsIHkgPSAiVG90YWwgUHJvZml0IikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKSAgIyBNb3ZlIHRoZSBsZWdlbmQgdG8gdGhlIHRvcApgYGAKIyMjIE9ic2VydmF0aW9ucwoKMS4gKipPdmVyYWxsIEluc2lnaHRzKio6CiAgIC0gKipEdWJsaW4qKiBzaWduaWZpY2FudGx5IG91dHBlcmZvcm1zIGFsbCBvdGhlciBsb2NhdGlvbnMgaW4gdG90YWwgcHJvZml0IGNvbnRyaWJ1dGlvbnMsIGlycmVzcGVjdGl2ZSBvZiBnZW5kZXIuCiAgIC0gTG9jYXRpb25zIHN1Y2ggYXMgKipDb3JrKiosICoqR2Fsd2F5KiosIGFuZCAqKkxpbWVyaWNrKiogZm9sbG93IER1YmxpbiwgYnV0IHRoZWlyIHByb2ZpdCBjb250cmlidXRpb25zIGFyZSBjb25zaWRlcmFibHkgbG93ZXIuCgoyLiAqKkdlbmRlci1CYXNlZCBEaXN0cmlidXRpb24qKjoKICAgLSAqKk1hbGUgY3VzdG9tZXJzKiogZG9taW5hdGUgdGhlIHRvdGFsIHByb2ZpdCBjb250cmlidXRpb25zIGFjcm9zcyBhbG1vc3QgYWxsIGxvY2F0aW9ucywgaGlnaGxpZ2h0aW5nIHRoZWlyIHNpZ25pZmljYW50IHJvbGUgaW4gcmV2ZW51ZSBnZW5lcmF0aW9uLgogICAtICoqRmVtYWxlIGN1c3RvbWVycyoqIHNob3cgbm90YWJsZSBjb250cmlidXRpb25zIGluIER1YmxpbiBhbmQgYSBmZXcgb3RoZXIgdG9wIGNpdGllcyBidXQgbGFnIGJlaGluZCBtYWxlIGN1c3RvbWVycyBvdmVyYWxsLgogICAtIFRoZSAqKk5vdCBEZWZpbmVkKiogZ2VuZGVyIGdyb3VwIGhhcyBtaW5pbWFsIHByb2ZpdCBjb250cmlidXRpb25zIGFuZCBpcyBtb3N0bHkgY29uY2VudHJhdGVkIGluIGEgZmV3IGxvY2F0aW9ucy4KCjMuICoqR2VvZ3JhcGhpY2FsIERpc3RyaWJ1dGlvbioqOgogICAtIEEgbG9uZyB0YWlsIG9mIHNtYWxsZXIgdG93bnMgY29udHJpYnV0ZXMgbWFyZ2luYWxseSB0byB0b3RhbCBwcm9maXQuIFRoZXNlIGxvY2F0aW9ucyBjb3VsZCBpbmRpY2F0ZSB1bnRhcHBlZCBwb3RlbnRpYWwgb3IgbG93IG1hcmtldCBwZW5ldHJhdGlvbi4KICAgLSBIaWdoIGNvbmNlbnRyYXRpb24gb2YgcHJvZml0IGluIG1ham9yIGNpdGllcyBsaWtlIER1YmxpbiBzdWdnZXN0cyBhIHN0cm9uZyB1cmJhbiBjdXN0b21lciBiYXNlLgoKLS0tCgojIyMgUmVjb21tZW5kYXRpb25zCgotIEZvY3VzIG1hcmtldGluZyBhbmQgY3VzdG9tZXIgZW5nYWdlbWVudCBlZmZvcnRzIG9uIGhpZ2gtcHJvZml0IGNpdGllcyBzdWNoIGFzICoqRHVibGluKiosICoqQ29yayoqLCBhbmQgKipHYWx3YXkqKi4KLSBEZXZlbG9wIHN0cmF0ZWdpZXMgdG8gaW5jcmVhc2UgZmVtYWxlIGN1c3RvbWVyIGVuZ2FnZW1lbnQgaW4gc21hbGxlciB0b3ducyBhbmQgY2l0aWVzLgotIEludmVzdGlnYXRlIHRoZSAiTm90IERlZmluZWQiIGdlbmRlciBncm91cCB0byB1bmRlcnN0YW5kIGl0cyBjaGFyYWN0ZXJpc3RpY3MgYW5kIHBvdGVudGlhbCBmb3IgZ3Jvd3RoLgotIEV4cGxvcmUgb3Bwb3J0dW5pdGllcyBpbiBzbWFsbGVyIHRvd25zIHdpdGggbG93IHByb2ZpdCBjb250cmlidXRpb25zIHRvIGV4cGFuZCBtYXJrZXQgcmVhY2guCgotLS0KCiMjIEltcGFjdCBvZiBQcm9tb3Rpb25zIG9uIFRvdGFsIFByb2ZpdAoKVGhpcyBzY2F0dGVyIHBsb3QgdmlzdWFsaXplcyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlICoqVG90YWwgVmFsdWUgb2YgQWxsIFByb21vdGlvbnMqKiBhbmQgdGhlICoqVG90YWwgUHJvZml0KiosIHNob3djYXNpbmcgaG93IHByb21vdGlvbmFsIGVmZm9ydHMgaW5mbHVlbmNlIG92ZXJhbGwgcHJvZml0YWJpbGl0eS4KCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9Nn0KIyBDcmVhdGUgc2NhdHRlciBwbG90IHdpdGggcmVncmVzc2lvbiBsaW5lCmdncGxvdChkZiwgYWVzKHggPSBUb3RhbF92YWx1ZV9vZl9hbGxfcHJvbW90aW9ucywgeSA9IFRvdGFsX1Byb2ZpdCkpICsKICBnZW9tX3BvaW50KGNvbG9yID0gY29sb3JzWzFdKSArICAjIFNjYXR0ZXIgcGxvdCB3aXRoIHBvaW50cyBjb2xvcmVkIGluIG9yYW5nZQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbG9yID0gY29sb3JzWzJdLCBzZSA9IEZBTFNFKSArICAjIEFkZCByZWdyZXNzaW9uIGxpbmUKICBsYWJzKAogICAgdGl0bGUgPSAiSW1wYWN0IG9mIFByb21vdGlvbnMgb24gVG90YWwgUHJvZml0IiwKICAgIHggPSAiVG90YWwgVmFsdWUgb2YgQWxsIFByb21vdGlvbnMiLAogICAgeSA9ICJUb3RhbCBQcm9maXQiCiAgKSArCiAgdGhlbWVfcm9zZV9waW5lKCkgICMgQXBwbHkgUm9zZSBQaW5lIHRoZW1lCmBgYAojIyMgT2JzZXJ2YXRpb25zCgoxLiAqKlBvc2l0aXZlIENvcnJlbGF0aW9uKio6CiAgIC0gVGhlIHNjYXR0ZXIgcGxvdCBkZW1vbnN0cmF0ZXMgYSAqKnBvc2l0aXZlIGNvcnJlbGF0aW9uKiogYmV0d2VlbiB0aGUgdG90YWwgdmFsdWUgb2YgcHJvbW90aW9ucyBhbmQgdG90YWwgcHJvZml0LiBBcyB0aGUgcHJvbW90aW9uYWwgdmFsdWUgaW5jcmVhc2VzLCB0aGUgcHJvZml0IGdlbmVyYWxseSByaXNlcy4KICAgLSBUaGUgZml0dGVkIHRyZW5kIGxpbmUgZnVydGhlciBzdXBwb3J0cyB0aGlzIHJlbGF0aW9uc2hpcCwgaW5kaWNhdGluZyB0aGF0IHByb21vdGlvbnMgZWZmZWN0aXZlbHkgY29udHJpYnV0ZSB0byBwcm9maXQgZ2VuZXJhdGlvbi4KCjIuICoqRGltaW5pc2hpbmcgUmV0dXJucyoqOgogICAtIEJleW9uZCBhIGNlcnRhaW4gcHJvbW90aW9uYWwgdmFsdWUgKH41MCB1bml0cyksIHRoZSBpbmNyZW1lbnRhbCBpbmNyZWFzZSBpbiBwcm9maXQgYmVjb21lcyBsZXNzIHByb25vdW5jZWQuCiAgIC0gVGhpcyBzdWdnZXN0cyAqKmRpbWluaXNoaW5nIHJldHVybnMqKiBvbiBpbnZlc3RtZW50IGluIHByb21vdGlvbnMsIHdoZXJlIGZ1cnRoZXIgc3BlbmRpbmcgbWF5IHlpZWxkIG1hcmdpbmFsIHByb2ZpdCBnYWlucy4KCjMuICoqSGlnaCBWYXJpYWJpbGl0eSoqOgogICAtIEEgaGlnaCBjb25jZW50cmF0aW9uIG9mIHBvaW50cyBhdCBsb3dlciBwcm9tb3Rpb25hbCB2YWx1ZXMgKH4wIHRvIDIwIHVuaXRzKSByZWZsZWN0cyB2YXJpYWJpbGl0eSBpbiBwcm9maXRzLCBpbmRpY2F0aW5nIHRoYXQgb3RoZXIgZmFjdG9ycyBtYXkgaW5mbHVlbmNlIGN1c3RvbWVyIGJlaGF2aW9yIGFuZCBwcm9maXRhYmlsaXR5LgoKLS0tCgojIyMgUmVjb21tZW5kYXRpb25zCgotICoqT3B0aW1pemUgUHJvbW90aW9uYWwgU3BlbmRpbmcqKjoKICAtIEZvY3VzIG9uIHByb21vdGlvbmFsIHZhbHVlcyB0aGF0IHlpZWxkIHRoZSBoaWdoZXN0IHJldHVybiBvbiBpbnZlc3RtZW50LCBwb3RlbnRpYWxseSB3aXRoaW4gdGhlIHJhbmdlIG9mIDIwIHRvIDUwIHVuaXRzLgotICoqQW5hbHl6ZSBIaWdoIFZhcmlhYmlsaXR5Kio6CiAgLSBJbnZlc3RpZ2F0ZSBmYWN0b3JzIGNvbnRyaWJ1dGluZyB0byB0aGUgdmFyaWFiaWxpdHkgaW4gcHJvZml0IGZvciBsb3dlciBwcm9tb3Rpb25hbCB2YWx1ZXMgdG8gcmVmaW5lIHN0cmF0ZWdpZXMuCi0gKipUYXJnZXRlZCBQcm9tb3Rpb25zKio6CiAgLSBEZXNpZ24gcHJvbW90aW9ucyB0YXJnZXRlZCBhdCBjdXN0b21lciBzZWdtZW50cyBtb3N0IHJlc3BvbnNpdmUgdG8gaW5jcmVhc2VkIHNwZW5kaW5nLgoKLS0tCgojIDQuIE91dGxpZXIgRGV0ZWN0aW9uIE1ldGhvZHMKClRoaXMgZG9jdW1lbnQgZGVtb25zdHJhdGVzIGhvdyB0byBkZXRlY3Qgb3V0bGllcnMgdXNpbmcgKipaLVNjb3JlKiosICoqTW9kaWZpZWQgWi1TY29yZSoqLCBhbmQgKipJUVIgbWV0aG9kcyoqIG9uIGEgc21hbGwgZGF0YXNldC4gV2Ugd2lsbCBjYWxjdWxhdGUgdGhlIG91dGxpZXJzIHN0ZXAtYnktc3RlcCBmb3IgYSBzaW1wbGUgNS1yb3cgZGF0YXNldC4KCi0tLQoKKipFeGFtcGxlIERhdGFzZXQqKgoKV2Ugd2lsbCB1c2UgdGhlIGZvbGxvd2luZyBkYXRhc2V0OgoKfCBSb3cgfCBWYWx1ZSB8CnwtLS0tLXwtLS0tLS0tfAp8IDEgICB8IDEwICAgIHwKfCAyICAgfCAxMiAgICB8CnwgMyAgIHwgMTQgICAgfAp8IDQgICB8IDEwMCAgIHwKfCA1ICAgfCAxNiAgICB8CgotLS0KCiMjIFotU2NvcmUgTWV0aG9kCgojIyMgRm9ybXVsYQpcWwpaID0gXGZyYWN7KFggLSBcbXUpfXtcc2lnbWF9ClxdCgpXaGVyZToKLSBcKFhcKTogVGhlIHZhbHVlLgotIFwoXG11XCk6IE1lYW4gb2YgdGhlIGRhdGFzZXQuCi0gXChcc2lnbWFcKTogU3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoZSBkYXRhc2V0LgoKIyMjIFN0ZXBzCjEuICoqQ29tcHV0ZSB0aGUgTWVhbiAoXChcbXVcKSkqKjoKICAgXFsKICAgXG11ID0gXGZyYWN7MTAgKyAxMiArIDE0ICsgMTAwICsgMTZ9ezV9ID0gMzAuNAogICBcXQoKMi4gKipDb21wdXRlIHRoZSBTdGFuZGFyZCBEZXZpYXRpb24gKFwoXHNpZ21hXCkpKio6CiAgIFxbCiAgIFxzaWdtYSA9IFxzcXJ0e1xmcmFje1xzdW17KFggLSBcbXUpXjJ9fXtufX0KICAgXF0KICAgU3Vic3RpdHV0aW5nIHZhbHVlczoKICAgXFsKICAgXHNpZ21hID0gXHNxcnR7XGZyYWN7KDEwLTMwLjQpXjIgKyAoMTItMzAuNCleMiArICgxNC0zMC40KV4yICsgKDEwMC0zMC40KV4yICsgKDE2LTMwLjQpXjJ9ezV9fQogICBcXQogICBcWwogICBcc2lnbWEgXGFwcHJveCAzOC44OQogICBcXQoKMy4gKipDb21wdXRlIFotU2NvcmVzKio6CiAgIFVzaW5nIFwoWiA9IFxmcmFjeyhYIC0gXG11KX17XHNpZ21hfVwpOgogICAtIFwoMTA6IFogXGFwcHJveCAtMC41MjRcKQogICAtIFwoMTI6IFogXGFwcHJveCAtMC40NzNcKQogICAtIFwoMTQ6IFogXGFwcHJveCAtMC40MjNcKQogICAtIFwoMTAwOiBaIFxhcHByb3ggMS43OTJcKQogICAtIFwoMTY6IFogXGFwcHJveCAtMC4zNzJcKQoKNC4gKipUaHJlc2hvbGQqKjogT3V0bGllcnMgYXJlIHZhbHVlcyB3aXRoIFwofFp8ID4gM1wpLgoKLS0tCgojIyBNb2RpZmllZCBaLVNjb3JlIE1ldGhvZAoKIyMjIEZvcm11bGEKXFsKTVogPSAwLjY3NDUgXGNkb3QgXGZyYWN7KFggLSBcdGV4dHttZWRpYW59KX17TUFEfQpcXQoKV2hlcmU6Ci0gXChYXCk6IFRoZSB2YWx1ZS4KLSBcKFx0ZXh0e21lZGlhbn1cKTogTWVkaWFuIG9mIHRoZSBkYXRhc2V0LgotIFwoTUFEXCk6IE1lZGlhbiBBYnNvbHV0ZSBEZXZpYXRpb246CiAgXFsKICBNQUQgPSBcdGV4dHtNZWRpYW59KHxYX2kgLSBcdGV4dHttZWRpYW59fCkKICBcXQoKIyMjIFN0ZXBzCjEuICoqQ29tcHV0ZSB0aGUgTWVkaWFuKio6CiAgIFxbCiAgIFx0ZXh0e01lZGlhbn0gPSAxNAogICBcXQoKMi4gKipDb21wdXRlIE1BRCoqOgogICBcWwogICB8MTAtMTR8ID0gNCwgXCwgfDEyLTE0fCA9IDIsIFwsIHwxNC0xNHwgPSAwLCBcLCB8MTAwLTE0fCA9IDg2LCBcLCB8MTYtMTR8ID0gMgogICBcXQogICBNZWRpYW4gb2YgYWJzb2x1dGUgZGV2aWF0aW9uczoKICAgXFsKICAgTUFEID0gXHRleHR7TWVkaWFufShbNCwgMiwgMCwgODYsIDJdKSA9IDIKICAgXF0KCjMuICoqQ29tcHV0ZSBNb2RpZmllZCBaLVNjb3JlcyoqOgogICBVc2luZyBcKE1aID0gMC42NzQ1IFxjZG90IFxmcmFjeyhYIC0gXHRleHR7bWVkaWFufSl9e01BRH1cKToKICAgLSBcKDEwOiBNWiBcYXBwcm94IC0xLjM0OVwpCiAgIC0gXCgxMjogTVogXGFwcHJveCAtMC42NzVcKQogICAtIFwoMTQ6IE1aIFxhcHByb3ggMFwpCiAgIC0gXCgxMDA6IE1aIFxhcHByb3ggMjguOTE2XCkKICAgLSBcKDE2OiBNWiBcYXBwcm94IDAuNjc1XCkKCjQuICoqVGhyZXNob2xkKio6IE91dGxpZXJzIGFyZSB2YWx1ZXMgd2l0aCBcKHxNWnwgPiAzLjVcKS4gSGVuY2UsIFwoMTAwXCkgaXMgYW4gb3V0bGllci4KCi0tLQoKIyMgSVFSIE1ldGhvZAoKIyMjIEZvcm11bGEKT3V0bGllcnMgYXJlIHZhbHVlcyBvdXRzaWRlOgpcWwpbXHRleHR7UTF9IC0gMS41IFxjZG90IFx0ZXh0e0lRUn0sIFx0ZXh0e1EzfSArIDEuNSBcY2RvdCBcdGV4dHtJUVJ9XQpcXQpXaGVyZToKLSBcKFExXCk6IDI1dGggcGVyY2VudGlsZS4KLSBcKFEzXCk6IDc1dGggcGVyY2VudGlsZS4KLSBcKFx0ZXh0e0lRUn0gPSBRMyAtIFExXCkuCgojIyMgU3RlcHMKMS4gKipDb21wdXRlIFF1YXJ0aWxlcyoqOgogICAtIFNvcnRlZCBkYXRhc2V0OiBcKFsxMCwgMTIsIDE0LCAxNiwgMTAwXVwpLgogICAtIFwoUTEgPSAxMiwgUTMgPSAxNlwpLgoKMi4gKipDb21wdXRlIElRUioqOgogICBcWwogICBcdGV4dHtJUVJ9ID0gUTMgLSBRMSA9IDE2IC0gMTIgPSA0CiAgIFxdCgozLiAqKkNvbXB1dGUgT3V0bGllciBCb3VuZHMqKjoKICAgLSBMb3dlciBCb3VuZDoKICAgICBcWwogICAgIFx0ZXh0e0xvd2VyIEJvdW5kfSA9IFExIC0gMS41IFxjZG90IFx0ZXh0e0lRUn0gPSAxMiAtIDEuNSBcY2RvdCA0ID0gNgogICAgIFxdCiAgIC0gVXBwZXIgQm91bmQ6CiAgICAgXFsKICAgICBcdGV4dHtVcHBlciBCb3VuZH0gPSBRMyArIDEuNSBcY2RvdCBcdGV4dHtJUVJ9ID0gMTYgKyAxLjUgXGNkb3QgNCA9IDIyCiAgICAgXF0KCjQuICoqSWRlbnRpZnkgT3V0bGllcnMqKjoKICAgLSBWYWx1ZXMgb3V0c2lkZSBcKFs2LCAyMl1cKSBhcmUgb3V0bGllcnMuIEluIHRoaXMgY2FzZSwgXCgxMDBcKSBpcyBhbiBvdXRsaWVyLgoKLS0tCgojIyBTdW1tYXJ5IFRhYmxlCgp8IFJvdyB8IFZhbHVlIHwgWi1TY29yZSB8IE1vZGlmaWVkIFotU2NvcmUgfCBJUVIgT3V0bGllciB8CnwtLS0tLXwtLS0tLS0tfC0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLXwKfCAxICAgfCAxMCAgICB8IC0wLjUyNCAgfCAtMS4zNDkgICAgICAgICAgIHwgTm8gICAgICAgICAgfAp8IDIgICB8IDEyICAgIHwgLTAuNDczICB8IC0wLjY3NSAgICAgICAgICAgfCBObyAgICAgICAgICB8CnwgMyAgIHwgMTQgICAgfCAtMC40MjMgIHwgMC4wMDAgICAgICAgICAgICB8IE5vICAgICAgICAgIHwKfCA0ICAgfCAxMDAgICB8IDEuNzkyICAgfCAyOC45MTYgICAgICAgICAgIHwgWWVzICAgICAgICAgfAp8IDUgICB8IDE2ICAgIHwgLTAuMzcyICB8IDAuNjc1ICAgICAgICAgICAgfCBObyAgICAgICAgICB8CgotLS0KCiMjIEtleSBPYnNlcnZhdGlvbnMKCi0gKipaLVNjb3JlKio6IERldGVjdHMgbm8gb3V0bGllcnMgKFwofFp8ID4gM1wpKS4KLSAqKk1vZGlmaWVkIFotU2NvcmUqKjogRGV0ZWN0cyBcKDEwMFwpIGFzIGFuIG91dGxpZXIgKFwofE1afCA+IDMuNVwpKS4KLSAqKklRUiBNZXRob2QqKjogRGV0ZWN0cyBcKDEwMFwpIGFzIGFuIG91dGxpZXIgKG91dHNpZGUgXChbNiwgMjJdXCkpLgoKLS0tCgojIyBDb25jbHVzaW9uCgotIFVzZSAqKklRUioqIGZvciBzaW1wbGUgRURBIG9yIHJvYnVzdCBjbGVhbmluZy4KLSBVc2UgKipNb2RpZmllZCBaLVNjb3JlKiogZm9yIHNrZXdlZCBkYXRhc2V0cyBvciBzbWFsbCBzYW1wbGUgc2l6ZXMuCi0gVXNlICoqWi1TY29yZSoqIGZvciBzeW1tZXRyaWMsIG5vcm1hbCBkaXN0cmlidXRpb25zLgoKCmBgYHtyfQojIEZpbHRlciBudW1lcmljIGNvbHVtbnMgb25seSBmcm9tIHNlbGVjdGVkX2NvbHMKbnVtZXJpY19kYXRhIDwtIGRmICU+JSBzZWxlY3QoYWxsX29mKHNlbGVjdGVkX2NvbHMpKSAlPiUgc2VsZWN0KHdoZXJlKGlzLm51bWVyaWMpKQoKIyMgMS4gWi1TQ09SRSBNRVRIT0QKel9zY29yZV9vdXRsaWVycyA8LSBudW1lcmljX2RhdGEgJT4lCiAgc3VtbWFyaXNlKGFjcm9zcyhldmVyeXRoaW5nKCksIH4gewogICAgel9zY29yZXMgPC0gc2NhbGUoLikgICMgU3RhbmRhcmRpemUgZGF0YQogICAgc3VtKGFicyh6X3Njb3JlcykgPiAzLCBuYS5ybSA9IFRSVUUpICAjIENvdW50IG91dGxpZXJzCiAgfSkpICU+JQogIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gIkNvbHVtbiIsIHZhbHVlc190byA9ICJaX1Njb3JlX091dGxpZXJzIikKCiMjIDIuIE1PRElGSUVEIFotU0NPUkUgTUVUSE9ECm1vZGlmaWVkX3pfb3V0bGllcnMgPC0gbnVtZXJpY19kYXRhICU+JQogIHN1bW1hcmlzZShhY3Jvc3MoZXZlcnl0aGluZygpLCB+IHsKICAgIG1lZGlhbl92YWwgPC0gbWVkaWFuKC4sIG5hLnJtID0gVFJVRSkKICAgIG1hZF92YWwgPC0gbWFkKC4sIGNvbnN0YW50ID0gMS40ODI2LCBuYS5ybSA9IFRSVUUpICAjIE1BRC1iYXNlZCBzY2FsZQogICAgbW9kaWZpZWRfeiA8LSAwLjY3NDUgKiAoLi1tZWRpYW5fdmFsKSAvIG1hZF92YWwKICAgIHN1bShhYnMobW9kaWZpZWRfeikgPiAzLjUsIG5hLnJtID0gVFJVRSkgICMgQ291bnQgcm9idXN0IG91dGxpZXJzCiAgfSkpICU+JQogIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gIkNvbHVtbiIsIHZhbHVlc190byA9ICJNb2RpZmllZF9aX091dGxpZXJzIikKCiMjIDMuIElRUiBNRVRIT0QKaXFyX291dGxpZXJzIDwtIG51bWVyaWNfZGF0YSAlPiUKICBzdW1tYXJpc2UoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgfiB7CiAgICBRMSA8LSBxdWFudGlsZSguLCAwLjI1LCBuYS5ybSA9IFRSVUUpCiAgICBRMyA8LSBxdWFudGlsZSguLCAwLjc1LCBuYS5ybSA9IFRSVUUpCiAgICBJUVIgPC0gUTMgLSBRMQogICAgbG93ZXJfYm91bmQgPC0gUTEgLSAxLjUgKiBJUVIKICAgIHVwcGVyX2JvdW5kIDwtIFEzICsgMS41ICogSVFSCiAgICBzdW0oLiA8IGxvd2VyX2JvdW5kIHwgLiA+IHVwcGVyX2JvdW5kLCBuYS5ybSA9IFRSVUUpICAjIENvdW50IElRUiBvdXRsaWVycwogIH0pKSAlPiUKICBwaXZvdF9sb25nZXIoZXZlcnl0aGluZygpLCBuYW1lc190byA9ICJDb2x1bW4iLCB2YWx1ZXNfdG8gPSAiSVFSX091dGxpZXJzIikKCiMjIENvbWJpbmUgUmVzdWx0cyBmb3IgQWxsIE1ldGhvZHMKb3V0bGllcl9zdW1tYXJ5IDwtIHpfc2NvcmVfb3V0bGllcnMgJT4lCiAgZnVsbF9qb2luKG1vZGlmaWVkX3pfb3V0bGllcnMsIGJ5ID0gIkNvbHVtbiIpICU+JQogIGZ1bGxfam9pbihpcXJfb3V0bGllcnMsIGJ5ID0gIkNvbHVtbiIpCgojIFByaW50IHRoZSBmdWxsIHN1bW1hcnkKcHJpbnQob3V0bGllcl9zdW1tYXJ5KQpgYGAKLS0tCgojIDUuIEhhbmRsaW5nIE91dGxpZXJzCgpXaGVuIGRlYWxpbmcgd2l0aCBvdXRsaWVyIHJlbW92YWwsIHR3byBwb3B1bGFyIElRUi1iYXNlZCBtZXRob2RzIGFyZToKCjEuICoqQ29sdW1uLVdpc2UgRmlsdGVyaW5nKio6IFJlbW92ZXMgb3V0bGllcnMgZm9yIGVhY2ggY29sdW1uIGluZGl2aWR1YWxseS4KMi4gKipSb3ctV2lzZSBGaWx0ZXJpbmcqKjogUmVtb3ZlcyBlbnRpcmUgcm93cyBpZiBhbnkgY29sdW1uIGNvbnRhaW5zIGFuIG91dGxpZXIuCgpUaGlzIGRvY3VtZW50IGV4cGxvcmVzIHRoZWlyIGNoYXJhY3RlcmlzdGljcywgYWR2YW50YWdlcywgZGlzYWR2YW50YWdlcywgYW5kIHVzZSBjYXNlcy4KCi0tLQoKIyMgQ29sdW1uLVdpc2UgRmlsdGVyaW5nCgpDb2x1bW4tV2lzZSBGaWx0ZXJpbmcgaXMgYSBsZXNzIGFnZ3Jlc3NpdmUgYXBwcm9hY2ggdG8gb3V0bGllciByZW1vdmFsLiBJdCBkZXRlY3RzIGFuZCByZW1vdmVzIG91dGxpZXJzIGZyb20gZWFjaCBjb2x1bW4gKippbmRpdmlkdWFsbHkqKiB3aGlsZSByZXRhaW5pbmcgcm93cyB0aGF0IGFyZSB2YWxpZCBmb3Igb3RoZXIgY29sdW1ucy4gVGhpcyBtZXRob2QgaXMgZXNwZWNpYWxseSB1c2VmdWwgZm9yIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgKEVEQSksIHdoZXJlIHJldGFpbmluZyBhcyBtdWNoIGRhdGEgYXMgcG9zc2libGUgaXMgZXNzZW50aWFsLgoKYGBge3J9CnJlbW92ZV9vdXRsaWVyc19jb2x1bW53aXNlIDwtIGZ1bmN0aW9uKGRhdGEsIGNvbHMpIHsKICBmb3IgKGNvbCBpbiBjb2xzKSB7CiAgICBRMSA8LSBxdWFudGlsZShkYXRhW1tjb2xdXSwgMC4yNSwgbmEucm0gPSBUUlVFKQogICAgUTMgPC0gcXVhbnRpbGUoZGF0YVtbY29sXV0sIDAuNzUsIG5hLnJtID0gVFJVRSkKICAgIElRUiA8LSBRMyAtIFExCiAgICBsb3dlcl9ib3VuZCA8LSBRMSAtIDEuNSAqIElRUgogICAgdXBwZXJfYm91bmQgPC0gUTMgKyAxLjUgKiBJUVIKICAgIGRhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKGRhdGFbW2NvbF1dID49IGxvd2VyX2JvdW5kICYgZGF0YVtbY29sXV0gPD0gdXBwZXJfYm91bmQpCiAgfQogIHJldHVybihkYXRhKQp9CmBgYAoKKipFeGFtcGxlIEV4ZWN1dGlvbioqCmBgYHtyfQpjbGVhbmVkX2RmX2NvbHVtbndpc2UgPC0gcmVtb3ZlX291dGxpZXJzX2NvbHVtbndpc2UoZGYsIHNlbGVjdGVkX2NvbHMpCmNhdCgiT3JpZ2luYWwgUm93czoiLCBucm93KGRmKSkKY2F0KCJDbGVhbmVkIFJvd3M6IiwgbnJvdyhjbGVhbmVkX2RmX2NvbHVtbndpc2UpKQpgYGAKCiMjIyBDaGFyYWN0ZXJpc3RpY3Mgb2YgQ29sdW1uLVdpc2UgRmlsdGVyaW5nCgojIyMjIEhvdyBJdCBXb3JrcwoKLSBPdXRsaWVycyBhcmUgZGV0ZWN0ZWQgYW5kIHJlbW92ZWQgZm9yIGVhY2ggY29sdW1uICoqaW5kZXBlbmRlbnRseSoqLgotIFJvd3MgYXJlIHJldGFpbmVkIHVubGVzcyBmbGFnZ2VkIGFzIGFuIG91dGxpZXIgaW4gdGhlICoqY3VycmVudCBjb2x1bW4qKiBiZWluZyBwcm9jZXNzZWQuCgojIyMjIEFkdmFudGFnZXMKCi0gUmV0YWlucyBtb3JlIGRhdGEgb3ZlcmFsbCwgYXMgcm93cyB2YWxpZCBpbiBvdGhlciBjb2x1bW5zIGFyZSAqKm5vdCByZW1vdmVkKiouCi0gTGVzcyBhZ2dyZXNzaXZlLCBtYWtpbmcgaXQgc3VpdGFibGUgZm9yICoqZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyAoRURBKSoqIG9yIGluaXRpYWwgY2xlYW5pbmcgc3RlcHMuCgojIyMjIERpc2FkdmFudGFnZXMKCi0gSW5jb25zaXN0ZW50IGNsZWFuaW5nOiBBIHJvdyBmbGFnZ2VkIGFzIGFuIG91dGxpZXIgaW4gb25lIGNvbHVtbiBidXQgdmFsaWQgaW4gYW5vdGhlciByZW1haW5zLCBwb3RlbnRpYWxseSBsZWFkaW5nIHRvICoqaW5jb25zaXN0ZW50IHJlc3VsdHMqKi4KCi0tLQoKIyMgUm93LVdpc2UgRmlsdGVyaW5nCgpgYGB7cn0KcmVtb3ZlX291dGxpZXJzX2lxciA8LSBmdW5jdGlvbihkYXRhLCBjb2xzKSB7CiAgZGF0YSAlPiUKICAgIGZpbHRlcihhY3Jvc3MoYWxsX29mKGNvbHMpLCB+IHsKICAgICAgUTEgPC0gcXVhbnRpbGUoLiwgMC4yNSwgbmEucm0gPSBUUlVFKQogICAgICBRMyA8LSBxdWFudGlsZSguLCAwLjc1LCBuYS5ybSA9IFRSVUUpCiAgICAgIElRUiA8LSBRMyAtIFExCiAgICAgIGxvd2VyX2JvdW5kIDwtIFExIC0gMS41ICogSVFSCiAgICAgIHVwcGVyX2JvdW5kIDwtIFEzICsgMS41ICogSVFSCiAgICAgIC4gPj0gbG93ZXJfYm91bmQgJiAuIDw9IHVwcGVyX2JvdW5kCiAgICB9KSkKfQpgYGAKCioqRXhhbXBsZSBFeGVjdXRpb24qKgpgYGB7cn0KIyBFeGFtcGxlOiBBcHBseWluZyBSb3ctV2lzZSBGaWx0ZXJpbmcKY2xlYW5lZF9kZl9yb3d3aXNlIDwtIHJlbW92ZV9vdXRsaWVyc19pcXIoZGYsIHNlbGVjdGVkX2NvbHMpCgojIE91dHB1dDogQmVmb3JlIGFuZCBBZnRlciBSb3cgQ291bnRzCmNhdCgiT3JpZ2luYWwgUm93czoiLCBucm93KGRmKSkKY2F0KCJDbGVhbmVkIFJvd3M6IiwgbnJvdyhjbGVhbmVkX2RmX3Jvd3dpc2UpKQpgYGAKCiMjIyBDaGFyYWN0ZXJpc3RpY3Mgb2YgUm93LVdpc2UgRmlsdGVyaW5nCgojIyMjIEhvdyBJdCBXb3JrcwoKLSBPdXRsaWVycyBhcmUgZGV0ZWN0ZWQgYWNyb3NzIGFsbCBzZWxlY3RlZCBjb2x1bW5zICoqYXQgdGhlIHNhbWUgdGltZSoqLgotIElmICoqYW55IGNvbHVtbioqIGluIGEgcm93IGNvbnRhaW5zIGFuIG91dGxpZXIsIHRoZSAqKmVudGlyZSByb3cgaXMgcmVtb3ZlZCoqLgoKIyMjIyBBZHZhbnRhZ2VzCgotIEVuc3VyZXMgKipjb25zaXN0ZW50IGNsZWFuaW5nKiogYWNyb3NzIGFsbCBzZWxlY3RlZCBjb2x1bW5zLgotIFN1aXRhYmxlIGZvciAqKm1hY2hpbmUgbGVhcm5pbmcgcGlwZWxpbmVzKiogb3IgKipzdHJpY3Qgc3RhdGlzdGljYWwgYW5hbHlzaXMqKiwgd2hlcmUgY2xlYW4gYW5kIGNvbXBsZXRlIGRhdGEgaXMgY3J1Y2lhbC4KCiMjIyMgRGlzYWR2YW50YWdlcwoKLSBNb3JlIGFnZ3Jlc3NpdmU6IFRoaXMgYXBwcm9hY2ggb2Z0ZW4gbGVhZHMgdG8gKipzaWduaWZpY2FudCBkYXRhIGxvc3MqKiB3aGVuIHZhcmlhYmlsaXR5IGV4aXN0cyBhY3Jvc3MgbXVsdGlwbGUgY29sdW1ucy4KCi0tLQoKIyMgV2hpY2ggT25lIFNob3VsZCBZb3UgVXNlPwoKU2VsZWN0aW5nIHRoZSBhcHByb3ByaWF0ZSBvdXRsaWVyIHJlbW92YWwgbWV0aG9kIGRlcGVuZHMgb24geW91ciBnb2FsLiBIZXJl4oCZcyBhIHN0cmFpZ2h0Zm9yd2FyZCBndWlkZSB0byBoZWxwIHlvdSBkZWNpZGUuCgotLS0KCioqSWYgWW91ciBHb2FsIGlzIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMgKEVEQSkgb3IgWW91IFdhbnQgdG8gUmV0YWluIGFzIE11Y2ggRGF0YSBhcyBQb3NzaWJsZToqKgoKLSAqKlVzZSoqOiBgcmVtb3ZlX291dGxpZXJzX2NvbHVtbndpc2VgCi0gKipXaHkgQ2hvb3NlIFRoaXMgTWV0aG9kPyoqCiAgLSBUaGlzIGFwcHJvYWNoIGlzICoqbGVzcyBhZ2dyZXNzaXZlKiosIHJlbW92aW5nIG91dGxpZXJzIGNvbHVtbiBieSBjb2x1bW4uCiAgLSBSb3dzIHRoYXQgYXJlIHZhbGlkIGluIG90aGVyIGNvbHVtbnMgcmVtYWluIGludGFjdCwgYWxsb3dpbmcgeW91IHRvIHJldGFpbiBtb3JlIG9mIHlvdXIgZGF0YXNldC4KICAtIFBlcmZlY3QgZm9yICoqaW5pdGlhbCBkYXRhIGNsZWFuaW5nKiogb3IgKiplYXJseS1zdGFnZSBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIChFREEpKiosIHdoZXJlIHByZXNlcnZpbmcgZGF0YSBmb3IgYSBicm9hZGVyIG92ZXJ2aWV3IGlzIGVzc2VudGlhbC4KCi0tLQoKKipJZiBZb3VyIEdvYWwgaXMgTWFjaGluZSBMZWFybmluZyBvciBTdGF0aXN0aWNhbCBNb2RlbGluZywgV2hlcmUgQ29uc2lzdGVuY3kgQWNyb3NzIFJvd3MgaXMgQ3JpdGljYWw6KioKCi0gKipVc2UqKjogYHJlbW92ZV9vdXRsaWVyc19pcXJgCi0gKipXaHkgQ2hvb3NlIFRoaXMgTWV0aG9kPyoqCiAgLSBUaGlzIG1ldGhvZCBpcyAqKm1vcmUgc3RyaW5nZW50KiosIHJlbW92aW5nIGVudGlyZSByb3dzIGlmIGFueSBjb2x1bW4gY29udGFpbnMgYW4gb3V0bGllci4KICAtIEVuc3VyZXMgdGhhdCB5b3VyIGRhdGFzZXQgaXMgKipjb25zaXN0ZW50KiogYW5kIGZyZWUgZnJvbSBvdXRsaWVycyBpbiB0aGUgc3BlY2lmaWVkIGNvbHVtbnMuCiAgLSBJZGVhbCBmb3IgKiptYWNoaW5lIGxlYXJuaW5nIHBpcGVsaW5lcyoqIG9yICoqc3RhdGlzdGljYWwgbW9kZWxpbmcqKiwgd2hlcmUgY2xlYW4gYW5kIGNvbnNpc3RlbnQgZGF0YSBpcyBjcnVjaWFsIGZvciBhY2N1cmF0ZSByZXN1bHRzLgoKLS0tCgojIyBRdWljayBDb21wYXJpc29uCgp8IEdvYWwgICAgICAgICAgICAgICAgICAgICB8IFJlY29tbWVuZGVkIE1ldGhvZCAgICAgICAgIHwgV2h5PyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfAp8ICoqRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyAoRURBKSoqIHwgYHJlbW92ZV9vdXRsaWVyc19jb2x1bW53aXNlYCB8IFJldGFpbnMgbW9yZSBkYXRhIGZvciBleHBsb3JhdGlvbi4gICAgfAp8ICoqTWFjaGluZSBMZWFybmluZy9Nb2RlbGluZyoqICAgICAgIHwgYHJlbW92ZV9vdXRsaWVyc19pcXJgICAgICAgIHwgRW5zdXJlcyBzdHJpY3QgY29uc2lzdGVuY3kgYWNyb3NzIHJvd3MufAoKLS0tCgojIyMgRmluYWwgVGhvdWdodHMKCi0gVXNlICoqQ29sdW1uLVdpc2UgRmlsdGVyaW5nKiogKGByZW1vdmVfb3V0bGllcnNfY29sdW1ud2lzZWApIHdoZW4gZXhwbG9yaW5nIGRhdGEgYW5kIGFpbWluZyB0byBwcmVzZXJ2ZSBhcyBtdWNoIGluZm9ybWF0aW9uIGFzIHBvc3NpYmxlLgotIFVzZSAqKlJvdy1XaXNlIEZpbHRlcmluZyoqIChgcmVtb3ZlX291dGxpZXJzX2lxcmApIHdoZW4gY29uc2lzdGVuY3kgYWNyb3NzIHJvd3MgaXMgY3JpdGljYWwgZm9yIGRvd25zdHJlYW0gdGFza3MgbGlrZSBtb2RlbGluZyBvciBhbmFseXNpcy4KCk5vdGU6IEFsd2F5cyBldmFsdWF0ZSB0aGUgaW1wYWN0IG9mIHlvdXIgY2hvc2VuIG1ldGhvZCBvbiB0aGUgZGF0YXNldCB0byBlbnN1cmUgaXQgYWxpZ25zIHdpdGggeW91ciBnb2Fscy4KCi0tLQoKIyMgVmlvbGluIFBsb3RzIENvbHVtbi1XaXNlIEZpbHRlcmluZwoKVmlvbGluIHBsb3RzIHByb3ZpZGUgYSBkZXRhaWxlZCByZXByZXNlbnRhdGlvbiBvZiB0aGUgZGlzdHJpYnV0aW9uIG9mIGRhdGEsIGNvbWJpbmluZyB0aGUgaW5mb3JtYXRpb24gb2YgYSBib3ggcGxvdCBhbmQgYSBkZW5zaXR5IHBsb3QuIFRoaXMgdmlzdWFsaXphdGlvbiBpcyBwYXJ0aWN1bGFybHkgdXNlZnVsIGZvciBpZGVudGlmeWluZyBwYXR0ZXJucywgc3ByZWFkLCBhbmQgcG90ZW50aWFsIG91dGxpZXJzIHdpdGhpbiBhIGRhdGFzZXQuCgpUaGUgZm9sbG93aW5nIHZpb2xpbiBwbG90cyBkaXNwbGF5IHRoZSBkaXN0cmlidXRpb25zIG9mIGtleSBmZWF0dXJlcyBpbiB0aGUgZGF0YXNldCAqKmFmdGVyIGFwcGx5aW5nIGNvbHVtbi13aXNlIG91dGxpZXIgcmVtb3ZhbCB1c2luZyB0aGUgSVFSIG1ldGhvZCoqLiBUaGUgc2VsZWN0ZWQgZmVhdHVyZXMgaW5jbHVkZToKCjEuICoqRmlyc3QgT3JkZXIgUHJvZml0KioKMi4gKipTdWJzZXF1ZW50IE9yZGVyIFByb2ZpdCoqCjMuICoqU3Vic2VxdWVudCBPcmRlcnMgQ291bnQqKgo0LiAqKlRvdGFsIFZhbHVlIG9mIEFsbCBQcm9tb3Rpb25zKioKNS4gKipBZ2UqKgo2LiAqKlRvdGFsIFByb2ZpdCoqCgotLS0KCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9OH0KIyBTdGVwIDM6IEdlbmVyYXRlIFZpb2xpbiBQbG90cyBEeW5hbWljYWxseQp2aW9saW5fcGxvdF9saXN0IDwtIGxhcHBseShzZXFfYWxvbmcoc2VsZWN0ZWRfY29scyksIGZ1bmN0aW9uKGkpIHsKICBjb2xfbmFtZSA8LSBzZWxlY3RlZF9jb2xzW2ldCiAgZm9ybWF0dGVkX3RpdGxlIDwtIHRvb2xzOjp0b1RpdGxlQ2FzZShnc3ViKCJfIiwgIiAiLCBjb2xfbmFtZSkpCiAgCiAgZ2dwbG90KGNsZWFuZWRfZGZfY29sdW1ud2lzZSwgYWVzKHggPSBmYWN0b3IoMSksIHkgPSAuZGF0YVtbY29sX25hbWVdXSkpICsKICAgIGdlb21fdmlvbGluKGZpbGwgPSBjb2xvcnNbaV0sIGNvbG9yID0gY29sb3JzW2ldLCBhbW91bnQgPSAwLjIpICsgICMgRHluYW1pYyBjb2xvcnMKICAgIGxhYnMoCiAgICAgIHRpdGxlID0gcGFzdGUoIiIsIGZvcm1hdHRlZF90aXRsZSksCiAgICAgIHggPSAiIiwgCiAgICAgIHkgPSAiVmFsdWUiCiAgICApICsKICAgIHRoZW1lX3Jvc2VfcGluZSgpICsgICMgQXBwbHkgUm9zZSBQaW5lIHRoZW1lCiAgICB0aGVtZSgKICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksICAjIFJlbW92ZSB4LWF4aXMgdGV4dAogICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksICAjIFJlbW92ZSB4LWF4aXMgdGlja3MKICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEsIGZhY2UgPSAiYm9sZCIpICAjIFRpdGxlIGZvcm1hdHRpbmcKICAgICkKfSkKCiMgU3RlcCA0OiBDb21iaW5lIEFsbCBWaW9saW4gUGxvdHMgaW50byBhIEdyaWQgTGF5b3V0CmdyaWQuYXJyYW5nZShncm9icyA9IHZpb2xpbl9wbG90X2xpc3QsIG5jb2wgPSBsZW5ndGgoc2VsZWN0ZWRfY29scykpCmBgYAojIyMgT2JzZXJ2YXRpb25zCgojIyMjIEZpcnN0IE9yZGVyIFByb2ZpdAotIFRoZSBtYWpvcml0eSBvZiB0aGUgdmFsdWVzIGFyZSBjb25jZW50cmF0ZWQgd2l0aGluIHRoZSBsb3dlciByYW5nZSwgaW5kaWNhdGluZyBtb3N0IGN1c3RvbWVycycgZmlyc3Qgb3JkZXJzIHlpZWxkIHJlbGF0aXZlbHkgbG93IHByb2ZpdC4KLSBBIHNtYWxsIHRhaWwgc3VnZ2VzdHMgYSBmZXcgY3VzdG9tZXJzIGhhdmUgc2lnbmlmaWNhbnRseSBoaWdoZXIgcHJvZml0cy4KCiMjIyMgU3Vic2VxdWVudCBPcmRlciBQcm9maXQKLSBTaW1pbGFyIHRvIHRoZSBmaXJzdCBvcmRlciwgdGhlIGRlbnNpdHkgaXMgY29uY2VudHJhdGVkIGF0IHRoZSBsb3dlciBlbmQgd2l0aCBmZXdlciBjdXN0b21lcnMgY29udHJpYnV0aW5nIHRvIGhpZ2hlciBwcm9maXRzLgoKIyMjIyBTdWJzZXF1ZW50IE9yZGVycyBDb3VudAotIE1vc3QgY3VzdG9tZXJzIGhhdmUgcGxhY2VkIGEgbGltaXRlZCBudW1iZXIgb2Ygc3Vic2VxdWVudCBvcmRlcnMuCi0gVGhlIGRpc3RyaWJ1dGlvbiByZXZlYWxzIGEgc21hbGwgbnVtYmVyIG9mIGN1c3RvbWVycyB3aXRoIG1hbnkgc3Vic2VxdWVudCBvcmRlcnMuCgojIyMjIFRvdGFsIFZhbHVlIG9mIEFsbCBQcm9tb3Rpb25zCi0gUHJvbW90aW9ucyBhcmUgY29uY2VudHJhdGVkIGF0IGxvdyB2YWx1ZXMsIHN1Z2dlc3RpbmcgbGltaXRlZCB1c2FnZSBvciBpbXBhY3QgZm9yIHRoZSBtYWpvcml0eSBvZiBjdXN0b21lcnMuCi0gVGhlIGRlbnNpdHkgcGxvdCBzaG93cyBhIHNoYXJwIGRlY2xpbmUgYXMgdGhlIHZhbHVlIGluY3JlYXNlcy4KCiMjIyMgQWdlCi0gQWdlIGRpc3RyaWJ1dGlvbiBpcyBmYWlybHkgdW5pZm9ybSBpbiB0aGUgbWlkZGxlIHJhbmdlICgyMOKAkzQwIHllYXJzKSwgdGFwZXJpbmcgb2ZmIGZvciB5b3VuZ2VyIGFuZCBvbGRlciBjdXN0b21lcnMuCgojIyMjIFRvdGFsIFByb2ZpdAotIFRoZSBvdmVyYWxsIHByb2ZpdCBpcyBwcmltYXJpbHkgbG93LCB3aXRoIGEgZmV3IGN1c3RvbWVycyBjb250cmlidXRpbmcgdG8gaGlnaCBwcm9maXRzLgoKLS0tCgojIyMgQWR2YW50YWdlcyBvZiBWaW9saW4gUGxvdHMKLSBDb21iaW5lcyB0aGUgZmVhdHVyZXMgb2YgYSBib3ggcGxvdCBhbmQgZGVuc2l0eSBwbG90LCBtYWtpbmcgaXQgZWFzaWVyIHRvIGlkZW50aWZ5IHRoZSBzcHJlYWQgYW5kIGRlbnNpdHkgb2YgZGF0YS4KLSBIaWdobGlnaHRzIHBvdGVudGlhbCBvdXRsaWVycyB2aXN1YWxseSB3aGlsZSByZXRhaW5pbmcgdGhlIG92ZXJhbGwgc2hhcGUgb2YgdGhlIGRpc3RyaWJ1dGlvbi4KCiMjIyBMaW1pdGF0aW9ucwotIERvZXMgbm90IHByb3ZpZGUgYSBkaXJlY3QgY291bnQgb2Ygb3V0bGllcnMuCi0gSW50ZXJwcmV0YXRpb24gY2FuIGJlIGNoYWxsZW5naW5nIGZvciBmZWF0dXJlcyB3aXRoIGhpZ2hseSBza2V3ZWQgZGlzdHJpYnV0aW9ucy4KCi0tLQoKIyMjIENvbmNsdXNpb24KClRoZSB2aW9saW4gcGxvdHMgb2ZmZXIgYSBjb21wcmVoZW5zaXZlIG92ZXJ2aWV3IG9mIHRoZSBjbGVhbmVkIGRhdGEgZGlzdHJpYnV0aW9ucywgaGlnaGxpZ2h0aW5nIHRoZSBjb25jZW50cmF0aW9uIG9mIHZhbHVlcywgdGhlIHNwcmVhZCwgYW5kIHRoZSBwcmVzZW5jZSBvZiBwb3RlbnRpYWwgb3V0bGllcnMuIFRoZXNlIHZpc3VhbGl6YXRpb25zIHNlcnZlIGFzIGEgdmFsdWFibGUgdG9vbCBmb3IgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyAoRURBKSBhbmQgcHJlcGFyaW5nIGRhdGEgZm9yIGZ1cnRoZXIgbW9kZWxpbmcuCgpgYGB7ciwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTh9CiMgU3Vic2V0IHRoZSBjbGVhbmVkIGRhdGFzZXQgdG8gdGhlIHNlbGVjdGVkIGNvbHVtbnMKY29ycmVsYXRpb25fZGF0YSA8LSBjbGVhbmVkX2RmX2NvbHVtbndpc2VbLCBzZWxlY3RlZF9jb2xzXQoKIyBDb21wdXRlIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXgKY29ycmVsYXRpb25fbWF0cml4IDwtIGNvcihjb3JyZWxhdGlvbl9kYXRhLCB1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIikKCiMgTWVsdCB0aGUgY29ycmVsYXRpb24gbWF0cml4IGZvciBnZ3Bsb3QKY29ycmVsYXRpb25fbWVsdGVkIDwtIG1lbHQoY29ycmVsYXRpb25fbWF0cml4KQoKIyBHZW5lcmF0ZSB0aGUgY29ycmVsYXRpb24gaGVhdG1hcApnZ3Bsb3QoZGF0YSA9IGNvcnJlbGF0aW9uX21lbHRlZCwgYWVzKHggPSBWYXIxLCB5ID0gVmFyMiwgZmlsbCA9IHZhbHVlKSkgKwogIGdlb21fdGlsZSgpICsgIyBBZGQgd2hpdGUgYm9yZGVyIGZvciB0aWxlcwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZCh2YWx1ZSwgMikpLCBzaXplID0gNikgKyAjIEFkZCBjb3JyZWxhdGlvbiBjb2VmZmljaWVudHMKICBzY2FsZV9maWxsX2dyYWRpZW50MigKICAgIGxvdyA9IGNvbG9yc1szXSwgCiAgICBoaWdoID0gY29sb3JzWzFdLCAKICAgIG1pZCA9IGNvbG9yc1syXSwgCiAgICBtaWRwb2ludCA9IDAsIAogICAgbGltaXRzID0gYygtMSwgMSksCiAgICBndWlkZSA9IGd1aWRlX2NvbG9yYmFyKAogICAgICB0aXRsZSA9ICIiLAogICAgICBiYXJ3aWR0aCA9IDQwLCAjIE1ha2UgdGhlIGNvbG9yIGJhciB3aWRlcgogICAgICBiYXJoZWlnaHQgPSAxICAjIEFkanVzdCB0aGUgaGVpZ2h0IG9mIHRoZSBiYXIKICAgICkKICApICsKICB0aGVtZV9yb3NlX3BpbmUoKSArICMgQXBwbHkgdGhlIFJvc8OpIFBpbmUgdGhlbWUKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgc2l6ZSA9IDE2KSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjI4KSwgIyBDZW50ZXIgYW5kIGFkanVzdCB0aXRsZSBzaXplCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLCAjIFJlbW92ZSBheGlzIHRpdGxlcwogICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgIyBSZW1vdmUgZ3JpZCBsaW5lcwogICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpLCAjIEFkanVzdCBsZWdlbmQgdGV4dCBzaXplCiAgKSsKICBnZ3RpdGxlKCJDb3JyZWxhdGlvbiBNYXRyaXgiKSAjIEFkZCBwbG90IHRpdGxlCmBgYApgYGB7ciwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTh9CiMgU2VsZWN0IGNhdGVnb3JpY2FsIGNvbHVtbnMKY2F0ZWdvcmljYWxfY29scyA8LSBjKCJHZW5kZXIiLCAiTG9jYXRpb24iLCAiQ29udGFjdF9BbGxvd2VkIiwgIk1hcmtldGluZ19DaGFubmVsX1R5cGUiKQoKIyBTdWJzZXQgdGhlIGRhdGFzZXQKY2F0ZWdvcmljYWxfZGF0YSA8LSBjbGVhbmVkX2RmX2NvbHVtbndpc2VbLCBjYXRlZ29yaWNhbF9jb2xzXQoKIyBEZWZpbmUgYSBmdW5jdGlvbiB0byBjb21wdXRlIENyYW3DqXIncyBWIGZvciBhIHBhaXIgb2YgY2F0ZWdvcmljYWwgZmVhdHVyZXMKY3JhbWVyc192X21hdHJpeCA8LSBmdW5jdGlvbihkYXRhKSB7CiAgbiA8LSBuY29sKGRhdGEpCiAgbWF0cml4IDwtIG1hdHJpeChOQSwgbiwgbiwgZGltbmFtZXMgPSBsaXN0KG5hbWVzKGRhdGEpLCBuYW1lcyhkYXRhKSkpCiAgZm9yIChpIGluIHNlcV9sZW4obikpIHsKICAgIGZvciAoaiBpbiBzZXFfbGVuKG4pKSB7CiAgICAgIGlmIChpIDw9IGopIHsKICAgICAgICBtYXRyaXhbaSwgal0gPC0gY3JhbWVyc1YoZGF0YVtbaV1dLCBkYXRhW1tqXV0sIHNpbXVsYXRlLnAudmFsdWUgPSBUUlVFKQogICAgICB9IGVsc2UgewogICAgICAgIG1hdHJpeFtpLCBqXSA8LSBtYXRyaXhbaiwgaV0KICAgICAgfQogICAgfQogIH0KICByZXR1cm4obWF0cml4KQp9CgojIENvbXB1dGUgdGhlIENyYW3DqXIncyBWIG1hdHJpeApjcmFtZXJzX3YgPC0gY3JhbWVyc192X21hdHJpeChjYXRlZ29yaWNhbF9kYXRhKQoKIyBNZWx0IHRoZSBtYXRyaXggZm9yIGdncGxvdAptZWx0ZWRfY3JhbWVyc192IDwtIG1lbHQoY3JhbWVyc192LCBuYS5ybSA9IFRSVUUpCgojIEdlbmVyYXRlIHRoZSBoZWF0bWFwCmdncGxvdChkYXRhID0gbWVsdGVkX2NyYW1lcnNfdiwgYWVzKHggPSBWYXIxLCB5ID0gVmFyMiwgZmlsbCA9IHZhbHVlKSkgKwogIGdlb21fdGlsZSgpICsgIyBBZGQgd2hpdGUgYm9yZGVycyBmb3IgdGlsZXMKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQodmFsdWUsIDIpKSwgc2l6ZSA9IDYpICsgIyBBZGQgQ3JhbcOpcidzIFYgdmFsdWVzCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIoCiAgICBsb3cgPSBjb2xvcnNbMl0sIAogICAgaGlnaCA9IGNvbG9yc1sxXSwgCiAgICBtaWQgPSBjb2xvcnNbM10sIAogICAgbWlkcG9pbnQgPSAwLjUsCiAgICBsaW1pdHMgPSBjKDAsIDEpLAogICAgZ3VpZGUgPSBndWlkZV9jb2xvcmJhcigKICAgICAgdGl0bGUgPSAiIiwKICAgICAgYmFyd2lkdGggPSAzMCwgIyBBZGp1c3QgY29sb3IgYmFyIHdpZHRoCiAgICAgIGJhcmhlaWdodCA9IDEgICMgQWRqdXN0IGNvbG9yIGJhciBoZWlnaHQKICAgICkKICApICsKICB0aGVtZV9yb3NlX3BpbmUoYmFzZV9zaXplID0gMTQpICsgIyBBcHBseSBSb3PDqSBQaW5lIHRoZW1lCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEsIHNpemUgPSAxNiksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMjAsIGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSwgIyBDZW50ZXIgdGhlIHRpdGxlCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLCAjIFJlbW92ZSBheGlzIHRpdGxlcwogICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgIyBSZW1vdmUgZ3JpZCBsaW5lcwogICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLCAjIEFkanVzdCBsZWdlbmQgdGV4dCBzaXplCiAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0LCBmYWNlID0gImJvbGQiKQogICkgKwogIGdndGl0bGUoIkNyYW3DqXIncyBWIENvcnJlbGF0aW9uIE1hdHJpeCBmb3IgQ2F0ZWdvcmljYWwgRmVhdHVyZXMiKSAjIEFkZCBwbG90IHRpdGxlCmBgYAoKYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD0yMH0KZ2dwYWlycygKICBkYXRhID0gY2xlYW5lZF9kZl9jb2x1bW53aXNlWywgc2VsZWN0ZWRfY29sc10sCiAgbWFwcGluZyA9IGFlcyhjb2xvciA9ICJUb3RhbF9Qcm9maXQiKSwgIyBSZXBsYWNlIHdpdGggYSByZWxldmFudCBjb2x1bW4gaWYgbmVlZGVkCiAgbG93ZXIgPSBsaXN0KAogICAgY29udGludW91cyA9IHdyYXAoInNtb290aCIsIGNvbG9yID0gY29sb3JzWzJdLCBzaXplID0gMC44KSwKICAgIGNvbWJvID0gd3JhcCgiZmFjZXRkZW5zaXR5IiwgYWxwaGEgPSAwLjcsIGZpbGwgPSBjb2xvcnNbM10pCiAgKSwKICB1cHBlciA9IGxpc3QoCiAgICBjb250aW51b3VzID0gd3JhcCgiY29yIiwgc2l6ZSA9IDYsIGNvbG9yID0gcm9zZV9waW5lX2NvbG9ycyR0ZXh0KQogICksCiAgZGlhZyA9IGxpc3QoCiAgICBjb250aW51b3VzID0gd3JhcCgiZGVuc2l0eURpYWciLCBmaWxsID0gY29sb3JzWzFdLCBhbHBoYSA9IDAuNykKICApCikgKwogIHRoZW1lX3Jvc2VfcGluZShiYXNlX3NpemUgPSAxMikgKyAjIEFwcGx5IHRoZSBSb3PDqSBQaW5lIHRoZW1lCiAgdGhlbWUoCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAzMCwgZmFjZSA9ICJib2xkIiksICMgVGl0bGUgZm9udCBzaXplCiAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIyKSwgIyBBeGlzIHRleHQgc2l6ZQogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMjIpLCAjIEF4aXMgdGl0bGUgc2l6ZQogICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpICMgU2l6ZSBvZiBmYWNldCBsYWJlbHMKICApICsKICBsYWJzKAogICAgdGl0bGUgPSAiUGFpcnBsb3Qgd2l0aCBDb3JyZWxhdGlvbiIKICApCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xNiwgZmlnLmhlaWdodD04fQojIFBhcnNlIFJlZ2lzdHJhdGlvbl9EYXRlIGFzIGRhdGV0aW1lCmNsZWFuZWRfZGZfY29sdW1ud2lzZSRSZWdpc3RyYXRpb25fRGF0ZSA8LSBtZHlfaG0oY2xlYW5lZF9kZl9jb2x1bW53aXNlJFJlZ2lzdHJhdGlvbl9EYXRlKQoKIyBFeHRyYWN0IHRoZSBtb250aCBhbmQgeWVhcgpjbGVhbmVkX2RmX2NvbHVtbndpc2UgPC0gY2xlYW5lZF9kZl9jb2x1bW53aXNlICU+JQogIG11dGF0ZShNb250aCA9IG1vbnRoKFJlZ2lzdHJhdGlvbl9EYXRlLCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBUUlVFKSkgIyBlLmcuLCBKYW4sIEZlYgoKbW9udGhseV9wcm9maXQgPC0gY2xlYW5lZF9kZl9jb2x1bW53aXNlICU+JQogIGdyb3VwX2J5KE1vbnRoKSAlPiUKICBzdW1tYXJpc2UoVG90YWxfUHJvZml0ID0gc3VtKFRvdGFsX1Byb2ZpdCwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShNb250aCkKCmdncGxvdChtb250aGx5X3Byb2ZpdCwgYWVzKHggPSBNb250aCwgeSA9IFRvdGFsX1Byb2ZpdCwgZ3JvdXAgPSAxKSkgKwogIGdlb21fbGluZShjb2xvciA9IGNvbG9yc1sxXSwgc2l6ZSA9IDEuMiwgbGluZXR5cGU9InR3b2Rhc2giKSArICMgTGluZSB3aXRoIGN1c3RvbSBjb2xvcgogIGdlb21fcG9pbnQoY29sb3IgPSBjb2xvcnNbMl0sIHNpemUgPSAzKSArICMgUG9pbnRzIG9uIHRoZSBsaW5lCiAgbGFicygKICAgIHRpdGxlID0gIk1vbnRobHkgVG90YWwgUHJvZml0IGluIDIwMTMiLAogICAgeCA9ICJNb250aCIsCiAgICB5ID0gIlRvdGFsIFByb2ZpdCIKICApICsKICB0aGVtZV9yb3NlX3BpbmUoYmFzZV9zaXplID0gMTUpICsKICB0aGVtZSgKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLCAjIENlbnRlcmVkIHRpdGxlCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KQogICkKCmBgYApgYGB7ciwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTZ9CiMgR3JvdXAgZGF0YSBieSBNYXJrZXRpbmdfQ2hhbm5lbF9UeXBlIGFuZCBNb250aCwgdGhlbiBjYWxjdWxhdGUgdGhlIHRvdGFsIHByb2ZpdAptb250aGx5X3Byb2ZpdF9ieV9jaGFubmVsIDwtIGNsZWFuZWRfZGZfY29sdW1ud2lzZSAlPiUKICBncm91cF9ieShNb250aCwgTWFya2V0aW5nX0NoYW5uZWxfVHlwZSkgJT4lCiAgc3VtbWFyaXNlKFRvdGFsX1Byb2ZpdCA9IHN1bShUb3RhbF9Qcm9maXQsIG5hLnJtID0gVFJVRSkpICU+JQogIGFycmFuZ2UoTW9udGgpCgojIENoZWNrIHRoZSB1bmlxdWUgbWFya2V0aW5nIGNoYW5uZWxzIGFuZCBjb2xvcnMKdW5pcXVlX2NoYW5uZWxzIDwtIHVuaXF1ZShtb250aGx5X3Byb2ZpdF9ieV9jaGFubmVsJE1hcmtldGluZ19DaGFubmVsX1R5cGUpCgojIEVuc3VyZSB0aGUgY29sb3JzIG1hdGNoIHRoZSB1bmlxdWUgY2hhbm5lbHMKY29sb3JzX2Zvcl9jaGFubmVscyA8LSBzZXROYW1lcyhjb2xvcnNbMTpsZW5ndGgodW5pcXVlX2NoYW5uZWxzKV0sIHVuaXF1ZV9jaGFubmVscykKCiMgUGxvdCB3aXRoIGNvbG9yIG1hcHBpbmcKZ2dwbG90KG1vbnRobHlfcHJvZml0X2J5X2NoYW5uZWwsIGFlcyh4ID0gTW9udGgsIHkgPSBUb3RhbF9Qcm9maXQsIGNvbG9yID0gTWFya2V0aW5nX0NoYW5uZWxfVHlwZSwgZ3JvdXAgPSBNYXJrZXRpbmdfQ2hhbm5lbF9UeXBlKSkgKwogIGdlb21fbGluZShzaXplID0gMS4yLCBsaW5ldHlwZT0idHdvZGFzaCIpICsgIyBMaW5lIGZvciBlYWNoIG1hcmtldGluZyBjaGFubmVsCiAgZ2VvbV9wb2ludChzaXplID0gMykgKyAjIFBvaW50cyBvbiB0aGUgbGluZXMKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY29sb3JzX2Zvcl9jaGFubmVscykgKyAjIE1hcCBjb2xvcnMgdG8gY2hhbm5lbHMKICBsYWJzKAogICAgdGl0bGUgPSAiTW9udGhseSBUb3RhbCBQcm9maXQgYnkgTWFya2V0aW5nIENoYW5uZWwgaW4gMjAxMyIsCiAgICB4ID0gIk1vbnRoIiwKICAgIHkgPSAiVG90YWwgUHJvZml0IiwKICAgIGNvbG9yID0gIk1hcmtldGluZyBDaGFubmVsIiAjIExlZ2VuZCB0aXRsZQogICkgKwogIHRoZW1lX3Jvc2VfcGluZShiYXNlX3NpemUgPSAxNSkgKyAjIEFwcGx5IFJvc8OpIFBpbmUgdGhlbWUKICB0aGVtZSgKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLCAjIENlbnRlcmVkIHRpdGxlCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpLAogICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsICMgTGVnZW5kIHBvc2l0aW9uCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLCAjIExlZ2VuZCB0ZXh0IHNpemUKICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYsIGZhY2UgPSAiYm9sZCIpICMgTGVnZW5kIHRpdGxlIHNpemUKICApCmBgYAoKIyA1LiBDb25jbHVzaW9uCgpUaGlzIHJlcG9ydCBoaWdobGlnaHRzIHRoZSBrZXkgbWV0cmljcywgbWFqb3IgZGF0YSBwb2ludHMsIGFuZCBjb3JyZWxhdGlvbnMgZnJvbSB0aGUgZGF0YXNldC4gVGhlIHZpc3VhbGl6YXRpb24gcHJvdmlkZXMgYSBjbGVhciBwaWN0dXJlIG9mIHRoZSBjaGFyYWN0ZXJpc3RpY3Mgb2YgZGlmZmVyZW50IGF0dHJpYnV0ZXMuCg==